import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In, DataSource } from 'typeorm';
import {
  AttendanceRecord,
  DingAttendanceRaw,
  Class,
  ClassSchedule,
  ClassStudent,
  StudentDingMapping,
  ClassTeacher,
  AttendanceDevice,
  TeacherRoleType,
} from '../entities';
import type {
  AttendanceSummaryQueryDto,
  QueryDingRawDto,
} from './dto/attendance.dto';
import { attachAttendanceDeviceMappings } from './attendance-device';
import { AttendanceCalendarService } from './attendance-calendar.service';
import { AttendanceRecordMutationService } from './attendance-record-mutation.service';
import { AttendanceReportService } from './attendance-report.service';

@Injectable()
export class AttendanceQueryService {
  constructor(
    @InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(DingAttendanceRaw) private dingRawRepo: Repository<DingAttendanceRaw>,
    @InjectRepository(Class) private classRepo: Repository<Class>,
    @InjectRepository(ClassSchedule) private scheduleRepo: Repository<ClassSchedule>,
    @InjectRepository(ClassStudent) private classStudentRepo: Repository<ClassStudent>,
    @InjectRepository(StudentDingMapping) private studentDingMappingRepo: Repository<StudentDingMapping>,
    @InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
    @InjectRepository(AttendanceDevice) private attendanceDeviceRepo: Repository<AttendanceDevice>,
    private dataSource: DataSource,
  ) {}

  private calendarService?: AttendanceCalendarService;
  private mutationService?: AttendanceRecordMutationService;
  private reportService?: AttendanceReportService;

  private get calendar(): AttendanceCalendarService {
    if (!this.calendarService) {
      this.calendarService = new AttendanceCalendarService(this.attendanceRepo, this.scheduleRepo);
    }
    return this.calendarService;
  }

  private get mutations(): AttendanceRecordMutationService {
    if (!this.mutationService) {
      this.mutationService = new AttendanceRecordMutationService(
        this.attendanceRepo,
        this.dingRawRepo,
        this.studentDingMappingRepo,
        this.dataSource,
      );
    }
    return this.mutationService;
  }

  private get reports(): AttendanceReportService {
    if (!this.reportService) {
      this.reportService = new AttendanceReportService(
        this.attendanceRepo,
        this.attendanceDeviceRepo,
      );
    }
    return this.reportService;
  }


  async getSummary(query: AttendanceSummaryQueryDto, accessibleClassIds?: number[]) {
    const qb = this.attendanceRepo.createQueryBuilder('ar');
    if (query.classId) {
      qb.andWhere('ar.classId = :classId', { classId: query.classId });
    }
    if (query.scheduleId) {
      qb.andWhere('ar.scheduleId = :scheduleId', { scheduleId: query.scheduleId });
    }
    if (!query.classId && accessibleClassIds) {
      if (accessibleClassIds.length === 0)
        return { total: 0, present: 0, late: 0, absent: 0, leave: 0, pending: 0, presentRate: 0 };
      qb.andWhere('ar.classId IN (:...accessibleClassIds)', { accessibleClassIds });
    }
    if (query.dateFrom) {
      qb.andWhere('ar.attendanceDate >= :dateFrom', { dateFrom: query.dateFrom });
    }
    if (query.dateTo) {
      qb.andWhere('ar.attendanceDate <= :dateTo', { dateTo: query.dateTo });
    }
    if (query.session) {
      qb.andWhere('ar.session = :session', { session: query.session });
    }

    const rows = await qb.getMany();

    const total = rows.length;
    const present = rows.filter((r) => r.status === 'present').length;
    const late = rows.filter((r) => r.status === 'late').length;
    const absent = rows.filter((r) => r.status === 'absent').length;
    const leave = rows.filter((r) => r.status === 'leave').length;
    const pending = rows.filter((r) => r.status === 'pending').length;
    const presentRate = total > 0 ? Number(((present / total) * 100).toFixed(1)) : 0;

    return { total, present, late, absent, leave, pending, presentRate };
  }

  // ── Attendance calendar ──
  async getCalendar(...args: Parameters<AttendanceCalendarService['getCalendar']>) {
    return this.calendar.getCalendar(...args);
  }

  async getScheduleOptionsForAttendance(
    ...args: Parameters<AttendanceCalendarService['getScheduleOptionsForAttendance']>
  ) {
    return this.calendar.getScheduleOptionsForAttendance(...args);
  }

  // ── List attendance records with filters ──
  async findAll(
    query: {
      classId?: number;
      scheduleId?: number;
      dateFrom?: string;
      dateTo?: string;
      session?: string;
      status?: string;
      source?: string;
      page?: number;
      pageSize?: number;
    },
    accessibleClassIds?: number[],
  ) {
    const page = query.page || 1;
    const pageSize = query.pageSize || 20;

    const qb = this.attendanceRepo.createQueryBuilder('ar');

    qb.leftJoinAndSelect('ar.student', 'student').leftJoinAndSelect('ar.class', 'class');
    if (query.scheduleId) {
      qb.andWhere('ar.scheduleId = :scheduleId', { scheduleId: query.scheduleId });
    }
    if (query.classId) {
      qb.andWhere('ar.classId = :classId', { classId: query.classId });
    } else if (accessibleClassIds) {
      if (accessibleClassIds.length === 0) return { list: [], total: 0, page, pageSize };
      qb.andWhere('ar.classId IN (:...accessibleClassIds)', { accessibleClassIds });
    }
    if (query.dateFrom) {
      qb.andWhere('ar.attendanceDate >= :dateFrom', { dateFrom: query.dateFrom });
    }
    if (query.dateTo) {
      qb.andWhere('ar.attendanceDate <= :dateTo', { dateTo: query.dateTo });
    }
    if (query.session) {
      qb.andWhere('ar.session = :session', { session: query.session });
    }
    if (query.status) {
      qb.andWhere('ar.status = :status', { status: query.status });
    }
    if (query.source) {
      qb.andWhere('ar.source = :source', { source: query.source });
    }

    qb.orderBy('ar.attendanceDate', 'DESC').addOrderBy('ar.createdAt', 'DESC');
    qb.skip((page - 1) * pageSize).take(pageSize);

    const [list, total] = await qb.getManyAndCount();
    return { list: await attachAttendanceDeviceMappings(list, this.attendanceDeviceRepo), total, page, pageSize };
  }

  // ── Get distinct classes with attendance records ──
  async getClasses(accessibleClassIds?: number[]) {
    const qb = this.attendanceRepo
      .createQueryBuilder('ar')
      .select('DISTINCT ar.classId', 'classId')
      .where('ar.classId IS NOT NULL');

    const rows: Array<{ classId: string | number }> = accessibleClassIds
      ? accessibleClassIds.map((classId) => ({ classId }))
      : await qb.orderBy('ar.classId', 'ASC').getRawMany();

    const classIds = [...new Set(rows.map((r) => Number(r.classId)).filter(Boolean))];
    if (classIds.length === 0) return [];

    const where = { id: In(classIds) };
    const [classes, teachers] = await Promise.all([
      this.classRepo.find({ where }),
      this.classTeacherRepo.find({
        where: {
          classId: In(classIds),
          roleType: In([
            TeacherRoleType.HEAD_TEACHER,
            TeacherRoleType.LIFE_TEACHER,
            TeacherRoleType.SUBJECT_TEACHER,
          ]),
        },
        relations: ['user'],
        order: { roleType: 'ASC', id: 'ASC' },
      }),
    ]);
    const nameMap = new Map(classes.map((c) => [c.id, c.name]));
    const teacherMap = new Map<
      number,
      Array<{
        userId: number;
        username: string | null;
        name: string | null;
        roleType: string;
        subject: string | null;
      }>
    >();

    for (const teacher of teachers) {
      const user = teacher.user as { username?: string | null; name?: string | null } | undefined;
      const items = teacherMap.get(teacher.classId) ?? [];
      items.push({
        userId: teacher.userId,
        username: user?.username || null,
        name: user?.name || null,
        roleType: teacher.roleType,
        subject: teacher.subject || null,
      });
      teacherMap.set(teacher.classId, items);
    }

    return classIds.map((id) => ({
      classId: id,
      className: nameMap.get(id) || `班级${id}`,
      teachers: teacherMap.get(id) ?? [],
    }));
  }

  // ── DingAttendance raw records ──
  async getDingRaw(query: QueryDingRawDto, accessibleClassIds?: number[]) {
    const page = query.page || 1;
    const pageSize = query.pageSize || 20;
    const qb = this.dingRawRepo.createQueryBuilder('ar');

    qb.leftJoinAndSelect('ar.matchedStudent', 'matchedStudent');
    if (query.matchStatus) {
      qb.andWhere('ar.matchStatus = :matchStatus', { matchStatus: query.matchStatus });
    }
    if (query.dateFrom) {
      qb.andWhere('ar.attendanceDate >= :dateFrom', { dateFrom: query.dateFrom });
    }
    if (query.dateTo) {
      qb.andWhere('ar.attendanceDate <= :dateTo', { dateTo: query.dateTo });
    }
    const scopedClassIds = query.classId ? [query.classId] : accessibleClassIds;
    if (scopedClassIds) {
      if (scopedClassIds.length === 0) return { list: [], total: 0, page, pageSize };
      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 { list: [], total: 0, page, pageSize };
      const mappings = await this.studentDingMappingRepo.find({
        where: { studentId: In(studentIds) },
      });
      const dingUserIds = [
        ...new Set(mappings.map((mapping) => mapping.dingUserId).filter(Boolean)),
      ];
      if (dingUserIds.length === 0) return { list: [], total: 0, page, pageSize };
      qb.andWhere('ar.dingUserId IN (:...dingUserIds)', { dingUserIds });
    }

    qb.orderBy('ar.attendanceDate', 'DESC')
      .addOrderBy('ar.checkInTime', 'ASC')
      .skip((page - 1) * pageSize)
      .take(pageSize);

    const [list, total] = await qb.getManyAndCount();
    return { list, total, page, pageSize };
  }

  // ── Match a dingtalk record to a student ──
  async matchDingRecord(...args: Parameters<AttendanceRecordMutationService['matchDingRecord']>) {
    return this.mutations.matchDingRecord(...args);
  }

  // ── Auto-match unmatched dingtalk records via dingUserId → userId mapping chain ──
  async autoMatchDingRecords(): Promise<{ matched: number; total: number }> {
    return this.mutations.autoMatchDingRecords();
  }

  // ── Export all attendance records with filters (no pagination) ──
  async findAllForExport(
    ...args: Parameters<AttendanceReportService['findAllForExport']>
  ) {
    return this.reports.findAllForExport(...args);
  }

  async findAttendanceRecord(
    ...args: Parameters<AttendanceRecordMutationService['findAttendanceRecord']>
  ) {
    return this.mutations.findAttendanceRecord(...args);
  }

  // ── Update a single attendance record ──
  async update(...args: Parameters<AttendanceRecordMutationService['update']>) {
    return this.mutations.update(...args);
  }

  // ── Delete a single attendance record ──
  async remove(...args: Parameters<AttendanceRecordMutationService['remove']>) {
    return this.mutations.remove(...args);
  }

  // ── Class-based attendance report ──
  async getReport(...args: Parameters<AttendanceReportService['getReport']>) {
    return this.reports.getReport(...args);
  }

  // ── Attendance alerts: detect consecutive absences/late ──
  async getAlerts(...args: Parameters<AttendanceReportService['getAlerts']>) {
    return this.reports.getAlerts(...args);
  }
}
