import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Like, Not, In, FindOptionsWhere, IsNull, Repository } from 'typeorm';
import { Student } from '../entities/student.entity';
import { Class } from '../entities/class.entity';
import { ClassStudent } from '../entities/class-student.entity';
import { ClassTeacher } from '../entities/class-teacher.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import { Organization } from '../entities/organization.entity';
import { Occupancy } from '../entities/occupancy.entity';
import { PersonalExpense } from '../entities/personal-expense.entity';
import { Bill } from '../entities/bill.entity';
import { Deposit } from '../entities/deposit.entity';
import { StudentProfile } from '../entities/student-profile.entity';
import { StudentEnrollment } from '../entities/student-enrollment.entity';
import { ExamScore } from '../entities/exam-score.entity';
import { LearningRecord } from '../entities/learning-record.entity';
import { ResultArchive } from '../entities/result-archive.entity';
import { ArchiveAttachment } from '../entities/archive-attachment.entity';
import { StudentDingMapping } from '../entities/student-ding-mapping.entity';
import { StudentWallet } from '../entities/student-wallet.entity';
import { RoomInspectionDetail } from '../entities/room-inspection-detail.entity';
import { CreateStudentDto, UpdateStudentDto } from './dto/student.dto';
import { assertActiveOrganization } from './students.organization';
import { StudentsImportService } from './students.import.service';
import { StudentsLifecycleService } from './students.lifecycle.service';
import { StudentsAgentService } from './students.agent.service';

@Injectable()
export class StudentsService {
  private importService?: StudentsImportService;
  private lifecycleService?: StudentsLifecycleService;
  private agentService?: StudentsAgentService;

  constructor(
    @InjectRepository(Student) private repo: Repository<Student>,
    @InjectRepository(ClassStudent) private classStudentRepo: Repository<ClassStudent>,
    @InjectRepository(Class) private classRepo: Repository<Class>,
    @InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
    @InjectRepository(Organization) private organizationRepo: Repository<Organization>,
    @InjectRepository(StudentProfile) private profileRepo: Repository<StudentProfile>,
    @InjectRepository(StudentEnrollment) private enrollmentRepo: Repository<StudentEnrollment>,
    @InjectRepository(ExamScore) private examScoreRepo: Repository<ExamScore>,
    @InjectRepository(LearningRecord) private learningRecordRepo: Repository<LearningRecord>,
    @InjectRepository(ResultArchive) private resultRepo: Repository<ResultArchive>,
    @InjectRepository(Occupancy) private occupancyRepo: Repository<Occupancy>,
    @InjectRepository(PersonalExpense) private personalExpenseRepo: Repository<PersonalExpense>,
    @InjectRepository(Bill) private billRepo: Repository<Bill>,
    @InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
    @InjectRepository(ArchiveAttachment) private attachmentRepo: Repository<ArchiveAttachment>,
    @InjectRepository(StudentDingMapping) private dingMappingRepo: Repository<StudentDingMapping>,
    @InjectRepository(StudentWallet) private walletRepo: Repository<StudentWallet>,
    @InjectRepository(RoomInspectionDetail)
    private inspectionDetailRepo: Repository<RoomInspectionDetail>,
  ) {}

  private get imports(): StudentsImportService {
    if (!this.importService) {
      this.importService = new StudentsImportService(
        this.repo,
        this.profileRepo,
        this.enrollmentRepo,
        this.examScoreRepo,
        this.learningRecordRepo,
        this.resultRepo,
        this.organizationRepo,
      );
    }
    return this.importService;
  }

  private get lifecycle(): StudentsLifecycleService {
    if (!this.lifecycleService) {
      this.lifecycleService = new StudentsLifecycleService(
        this.repo,
        this.classStudentRepo,
        this.attendanceRepo,
        this.profileRepo,
        this.enrollmentRepo,
        this.examScoreRepo,
        this.learningRecordRepo,
        this.resultRepo,
        this.occupancyRepo,
        this.personalExpenseRepo,
        this.billRepo,
        this.depositRepo,
        this.attachmentRepo,
        this.dingMappingRepo,
        this.walletRepo,
        this.inspectionDetailRepo,
      );
    }
    return this.lifecycleService;
  }

  private get agents(): StudentsAgentService {
    if (!this.agentService) {
      this.agentService = new StudentsAgentService(this.repo, this.classStudentRepo);
    }
    return this.agentService;
  }

  async getAccessibleClassIds(userId: number, canManageAll = false): Promise<number[] | undefined> {
    if (canManageAll) return undefined;
    const assignments = await this.classTeacherRepo.find({ where: { userId } });
    return [...new Set(assignments.map((assignment) => assignment.classId))];
  }

  async getBasicLookups() {
    return this.repo.find({
      select: ['id', 'name', 'studentNo', 'gender', 'phone', 'status'],
      where: { status: 'active' },
      order: { name: 'ASC' },
    });
  }

  async getArchiveExportMaps(...args: Parameters<StudentsLifecycleService['getArchiveExportMaps']>) {
    return this.lifecycle.getArchiveExportMaps(...args);
  }

  async findAll(
    query?: {
      name?: string;
      status?: string;
      includeArchived?: boolean;
      organizationId?: number | string;
      classId?: number | string;
      teacherId?: number | string;
    },
    accessibleClassIds?: number[],
  ) {
    const where: FindOptionsWhere<Student> = {};
    if (query?.name) where.name = Like(`%${query.name}%`);
    if (query?.organizationId) where.organizationId = Number(query.organizationId);
    if (query?.status) {
      where.status = query.status;
    } else if (!query?.includeArchived) {
      where.status = Not(In(['archived', 'staff']));
    }

    let scopedClassIds = accessibleClassIds ? [...accessibleClassIds] : undefined;
    if (query?.teacherId) {
      const teacherAssignments = await this.classTeacherRepo.find({
        where: { userId: Number(query.teacherId) },
      });
      const teacherClassIds = [...new Set(teacherAssignments.map((item) => item.classId))];
      scopedClassIds = scopedClassIds
        ? scopedClassIds.filter((classId) => teacherClassIds.includes(classId))
        : teacherClassIds;
    }
    if (query?.classId) {
      const classId = Number(query.classId);
      scopedClassIds = scopedClassIds
        ? scopedClassIds.filter((accessibleClassId) => accessibleClassId === classId)
        : [classId];
    }

    if (scopedClassIds) {
      if (scopedClassIds.length === 0) return [];
      const classStudents = await this.classStudentRepo.find({
        where: { classId: In(scopedClassIds), status: 'active' },
      });
      const studentIds = [...new Set(classStudents.map((item) => item.studentId))];
      if (studentIds.length === 0) return [];
      where.id = In(studentIds);
    }
    return this.repo.find({ where, order: { createdAt: 'DESC' }, relations: ['organization'] });
  }

  async getFilterLookups(accessibleClassIds?: number[]) {
    if (accessibleClassIds && accessibleClassIds.length === 0) {
      return { classes: [], teachers: [] };
    }

    const classWhere = accessibleClassIds
      ? { id: In(accessibleClassIds), isArchived: false }
      : { isArchived: false };
    const classes = await this.classRepo.find({
      select: ['id', 'name', 'code'],
      where: classWhere,
      order: { name: 'ASC' },
    });

    const teacherWhere = accessibleClassIds
      ? { classId: In(accessibleClassIds) }
      : { classId: In(classes.map((item) => item.id)), userId: Not(IsNull()) };
    const assignments = classes.length
      ? await this.classTeacherRepo.find({ where: teacherWhere, relations: ['user'] })
      : [];
    const teacherMap = new Map<number, { id: number; name: string; username: string }>();
    for (const assignment of assignments) {
      if (!assignment.user || assignment.user.isArchived) continue;
      teacherMap.set(assignment.userId, {
        id: assignment.userId,
        name: assignment.user.name || assignment.user.username,
        username: assignment.user.username,
      });
    }

    return {
      classes: classes.map((item) => ({ id: item.id, name: item.name, code: item.code })),
      teachers: [...teacherMap.values()].sort((a, b) => a.name.localeCompare(b.name, 'zh-CN')),
    };
  }

  async findOne(id: number) {
    const student = await this.repo.findOne({
      where: { id },
      relations: ['occupancies', 'occupancies.room'],
    });
    if (!student) throw new NotFoundException('学生不存在');
    return student;
  }

  async create(dto: CreateStudentDto) {
    await assertActiveOrganization(this.organizationRepo, dto.organizationId);
    return this.repo.save(this.repo.create(dto));
  }

  async update(id: number, dto: UpdateStudentDto) {
    await this.findOne(id);
    if (dto.organizationId) await assertActiveOrganization(this.organizationRepo, dto.organizationId);
    await this.repo.update(id, dto);
    return this.repo.findOne({ where: { id } });
  }

  async remove(id: number) {
    const student = await this.findOne(id);
    if (student.status === 'archived') {
      throw new BadRequestException('该学生已归档');
    }
    await this.repo.update(id, { status: 'archived' });
    return { message: '已归档（数据已保留，可随时恢复）' };
  }

  async batchRemove(...args: Parameters<StudentsLifecycleService['batchRemove']>) {
    return this.lifecycle.batchRemove(...args);
  }

  async restore(...args: Parameters<StudentsLifecycleService['restore']>) {
    return this.lifecycle.restore(...args);
  }

  async purge(...args: Parameters<StudentsLifecycleService['purge']>) {
    return this.lifecycle.purge(...args);
  }

  async batchPurge(...args: Parameters<StudentsLifecycleService['batchPurge']>) {
    return this.lifecycle.batchPurge(...args);
  }

  async batchRestore(...args: Parameters<StudentsLifecycleService['batchRestore']>) {
    return this.lifecycle.batchRestore(...args);
  }

  async batchImport(...args: Parameters<StudentsImportService['batchImport']>) {
    return this.imports.batchImport(...args);
  }

  async matchImport(...args: Parameters<StudentsImportService['matchImport']>) {
    return this.imports.matchImport(...args);
  }

  async compareClasses(studentId: number) {
    const student = await this.repo.findOne({ where: { id: studentId } });
    if (!student) throw new NotFoundException('学生不存在');

    const enrollments = await this.classStudentRepo.find({
      where: { studentId },
      relations: ['class'],
    });

    if (enrollments.length === 0) {
      return { student, enrollments: [] };
    }

    const classIds = enrollments.map((e) => e.classId);
    const attendanceRecords = await this.attendanceRepo.find({
      where: { studentId, classId: In(classIds) },
    });

    const attendanceByClass = new Map<number, AttendanceRecord[]>();
    for (const r of attendanceRecords) {
      const list = attendanceByClass.get(r.classId) || [];
      list.push(r);
      attendanceByClass.set(r.classId, list);
    }

    const comparison = enrollments.map((e) => {
      const records = attendanceByClass.get(e.classId) || [];
      const total = records.length;
      const present = records.filter((r) => r.status === 'present' || r.status === '正常').length;
      const absent = records.filter((r) => r.status === 'absent' || r.status === '缺勤').length;
      const late = records.filter((r) => r.status === 'late' || r.status === '迟到').length;
      const leave = records.filter((r) => r.status === 'leave' || r.status === '请假').length;

      return {
        classId: e.classId,
        className: e.class?.name || '',
        classType: e.class?.classType || '',
        startDate: e.class?.startDate || '',
        endDate: e.class?.endDate || '',
        joinDate: e.joinDate,
        leaveDate: e.leaveDate,
        status: e.status,
        attendanceStats: {
          total,
          present,
          absent,
          late,
          leave,
          rate: total > 0 ? Math.round((present / total) * 100) : 0,
        },
      };
    });

    return { student, enrollments: comparison };
  }

  async agentSearchStudents(
    ...args: Parameters<StudentsAgentService['agentSearchStudents']>
  ) {
    return this.agents.agentSearchStudents(...args);
  }

  async agentGetStudentBasic(
    ...args: Parameters<StudentsAgentService['agentGetStudentBasic']>
  ) {
    return this.agents.agentGetStudentBasic(...args);
  }
}
