import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import {
  AttendanceRecord,
  DingAttendanceRaw,
  StudentDingMapping,
  AttendanceSession,
} from '../entities';
import type { MatchDingRecordDto, UpdateAttendanceRecordDto } from './dto/attendance.dto';
import { SessionMutex } from './attendance-mutex';

@Injectable()
export class AttendanceRecordMutationService {
  private readonly sessionMutex = new SessionMutex();

  constructor(
    @InjectRepository(AttendanceRecord)
    private attendanceRepo: Repository<AttendanceRecord>,
    @InjectRepository(DingAttendanceRaw)
    private dingRawRepo: Repository<DingAttendanceRaw>,
    @InjectRepository(StudentDingMapping)
    private studentDingMappingRepo: Repository<StudentDingMapping>,
    private dataSource: DataSource,
  ) {}

  async findAttendanceRecord(id: number) {
    const record = await this.attendanceRepo.findOne({ where: { id } });
    if (!record) {
      throw new NotFoundException(`AttendanceRecord ${id} not found`);
    }
    return record;
  }

  // ── Match a dingtalk record to a student ──
  async matchDingRecord(id: number, dto: MatchDingRecordDto) {
    const record = await this.dingRawRepo.findOne({ where: { id } });
    if (!record) {
      throw new NotFoundException(`DingAttendanceRaw ${id} not found`);
    }

    record.matchedStudentId = dto.studentId;
    record.matchStatus = 'matched';
    return this.dingRawRepo.save(record);
  }

  // ── Auto-match unmatched dingtalk records via dingUserId → userId mapping chain ──
  async autoMatchDingRecords(): Promise<{ matched: number; total: number }> {
    const unmatched = await this.dingRawRepo.find({
      where: { matchStatus: 'unmatched' },
    });

    if (unmatched.length === 0) return { matched: 0, total: 0 };

    const mappings = await this.studentDingMappingRepo.find();
    const dingToStudentId = new Map<string, number>();
    for (const m of mappings) {
      dingToStudentId.set(m.dingUserId, m.studentId);
    }

    let matched = 0;
    for (const record of unmatched) {
      const studentId = dingToStudentId.get(record.dingUserId);
      if (studentId == null) continue;

      record.matchedStudentId = studentId;
      record.matchStatus = 'matched';
      await this.dingRawRepo.save(record);
      matched++;
    }

    return { matched, total: unmatched.length };
  }

  // ── Update a single attendance record ──
  async update(id: number, dto: UpdateAttendanceRecordDto) {
    const record = await this.findAttendanceRecord(id);

    // Records without a lesson session keep original behaviour
    if (record.attendanceSessionId == null) {
      if (dto.status !== undefined) {
        record.status = dto.status;
        record.source = 'manual';
        record.punchTime = null;
        record.punchSource = null;
        record.punchDeviceName = null;
        record.punchDeviceId = null;
      }
      if (dto.remark !== undefined) {
        record.remark = dto.remark;
        record.source = 'manual';
      }
      return this.attendanceRepo.save(record);
    }

    return this.sessionMutex.runExclusive(record.attendanceSessionId, () =>
      this.dataSource.transaction(async (manager) => {
        const recordRepo = manager.getRepository(AttendanceRecord);
        const sessionRepo = manager.getRepository(AttendanceSession);

        // Re-check session status inside the transaction while holding the lock
        const session = await sessionRepo.findOne({ where: { id: record.attendanceSessionId! } });
        if (!session || session.status === 'completed') {
          throw new BadRequestException('已完成考勤的记录不允许修改或删除');
        }

        const freshRecord = await recordRepo.findOne({ where: { id } });
        if (!freshRecord) throw new NotFoundException(`AttendanceRecord ${id} not found`);

        if (dto.status !== undefined) {
          freshRecord.status = dto.status;
          freshRecord.source = 'manual';
          freshRecord.punchTime = null;
          freshRecord.punchSource = null;
          freshRecord.punchDeviceName = null;
          freshRecord.punchDeviceId = null;
        }
        if (dto.remark !== undefined) {
          freshRecord.remark = dto.remark;
          freshRecord.source = 'manual';
        }
        return recordRepo.save(freshRecord);
      }),
    );
  }

  // ── Delete a single attendance record ──
  async remove(id: number) {
    const record = await this.findAttendanceRecord(id);

    // Records without a lesson session keep original behaviour
    if (record.attendanceSessionId == null) {
      await this.attendanceRepo.remove(record);
      return { deleted: true };
    }

    return this.sessionMutex.runExclusive(record.attendanceSessionId, () =>
      this.dataSource.transaction(async (manager) => {
        const recordRepo = manager.getRepository(AttendanceRecord);
        const sessionRepo = manager.getRepository(AttendanceSession);

        // Re-check session status inside the transaction while holding the lock
        const session = await sessionRepo.findOne({ where: { id: record.attendanceSessionId! } });
        if (!session || session.status === 'completed') {
          throw new BadRequestException('已完成考勤的记录不允许修改或删除');
        }

        const freshRecord = await recordRepo.findOne({ where: { id } });
        if (!freshRecord) throw new NotFoundException(`AttendanceRecord ${id} not found`);

        await recordRepo.remove(freshRecord);
        return { deleted: true };
      }),
    );
  }
}
