import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AttendanceRecord, AttendanceDevice } from '../entities';
import type { AttendanceReportQueryDto } from './dto/attendance.dto';
import { attachAttendanceDeviceMappings } from './attendance-device';

@Injectable()
export class AttendanceReportService {
  constructor(
    @InjectRepository(AttendanceRecord)
    private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(AttendanceDevice)
    private attendanceDeviceRepo: Repository<AttendanceDevice>,
  ) {}

  // ── Export all attendance records with filters (no pagination) ──
  async findAllForExport(
    query: {
      classId?: number;
      scheduleId?: number;
      dateFrom?: string;
      dateTo?: string;
      session?: string;
      status?: string;
      source?: string;
    },
    accessibleClassIds?: number[],
  ) {
    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 [];
      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');

    const records = await qb.getMany();
    return attachAttendanceDeviceMappings(records, this.attendanceDeviceRepo);
  }

  // ── Class-based attendance report ──
  async getReport(query: AttendanceReportQueryDto, accessibleClassIds?: number[]) {
    const qb = this.attendanceRepo.createQueryBuilder('ar');

    qb.leftJoin('ar.class', 'class')
      .select('class.id', 'classId')
      .addSelect('class.name', 'className')
      .addSelect('ar.status', 'status')
      .addSelect('COUNT(*)', 'count');
    if (query.classId) {
      qb.andWhere('ar.classId = :classId', { classId: query.classId });
    } else if (accessibleClassIds) {
      if (accessibleClassIds.length === 0) return [];
      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 });
    }

    qb.groupBy('class.id').addGroupBy('class.name').addGroupBy('ar.status');
    const rawRows = await qb.getRawMany();

    // Aggregate by class
    const classMap = new Map<
      number,
      {
        classId: number;
        className: string;
        present: number;
        absent: number;
        late: number;
        leave: number;
      }
    >();

    for (const row of rawRows as Array<{
      classId?: number;
      className?: string | null;
      status?: string;
      count: string;
    }>) {
      if (!row.classId) continue;
      if (!classMap.has(row.classId)) {
        classMap.set(row.classId, {
          classId: row.classId,
          className: row.className || `班级#${row.classId}`,
          present: 0,
          absent: 0,
          late: 0,
          leave: 0,
        });
      }
      const entry = classMap.get(row.classId)!;
      const count = parseInt(row.count, 10);
      if (row.status === 'present') entry.present += count;
      else if (row.status === 'absent') entry.absent += count;
      else if (row.status === 'late') entry.late += count;
      else if (row.status === 'leave') entry.leave += count;
    }

    return Array.from(classMap.values()).map((entry) => {
      const total = entry.present + entry.absent + entry.late + entry.leave;
      return {
        ...entry,
        total,
        presentRate: total > 0 ? ((entry.present / total) * 100).toFixed(1) : '0.0',
        absentRate: total > 0 ? ((entry.absent / total) * 100).toFixed(1) : '0.0',
        lateRate: total > 0 ? ((entry.late / total) * 100).toFixed(1) : '0.0',
        leaveRate: total > 0 ? ((entry.leave / total) * 100).toFixed(1) : '0.0',
      };
    });
  }

  // ── Attendance alerts: detect consecutive absences/late ──
  async getAlerts(days: number = 14, threshold: number = 3, accessibleClassIds?: number[]) {
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - days);
    const cutoffStr = cutoff.toISOString().slice(0, 10);

    const qb = this.attendanceRepo
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.student', 'student')
      .leftJoinAndSelect('a.class', 'class');

    qb.where('a.attendanceDate >= :cutoff', { cutoff: cutoffStr }).andWhere(
      'a.status IN (:...statuses)',
      { statuses: ['absent', 'late'] },
    );
    if (accessibleClassIds) {
      if (accessibleClassIds.length === 0) return [];
      qb.andWhere('a.classId IN (:...accessibleClassIds)', { accessibleClassIds });
    }
    const records = await qb
      .orderBy('a.studentId', 'ASC')
      .addOrderBy('a.attendanceDate', 'DESC')
      .getMany();

    const alerts: Array<{
      studentId: number;
      studentName: string;
      className: string;
      type: string;
      count: number;
      lastDate: string;
    }> = [];

    let current: (typeof alerts)[0] | null = null;
    for (const r of records) {
      const name = r.student?.name || '';
      const className = r.class?.name || '';
      const status = r.status === 'absent' ? '缺勤' : '迟到';
      if (current && current.studentId === r.studentId && current.type === status) {
        current.count++;
        if (r.attendanceDate > current.lastDate) current.lastDate = r.attendanceDate;
      } else {
        if (current && current.count >= threshold) alerts.push({ ...current });
        current = {
          studentId: r.studentId,
          studentName: name,
          className,
          type: status,
          count: 1,
          lastDate: r.attendanceDate,
        };
      }
    }
    if (current && current.count >= threshold) alerts.push(current);
    return alerts;
  }
}
