import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DingLeaveRaw, Student, StudentDingMapping } from '../entities';
import { DingTalkService, DingTalkLeaveResult } from '../integration/dingtalk.service';

/**
 * 钉钉请假数据同步服务。
 *
 * 钉钉「获取用户考勤数据」接口按 用户 × 工作日 返回当天审批单列表，
 * 这里只保留 biz_type=3（请假）且已审批完成的数据。逐用户逐日请求，
 * 单条失败只记录错误、不中断整批，避免请假数据缺失阻断课程结算。
 */
@Injectable()
export class AttendanceLeaveSyncService {
  private readonly logger = new Logger(AttendanceLeaveSyncService.name);

  constructor(
    @InjectRepository(DingLeaveRaw)
    private readonly dingLeaveRawRepo: Repository<DingLeaveRaw>,
    @InjectRepository(Student)
    private readonly studentRepo: Repository<Student>,
    @InjectRepository(StudentDingMapping)
    private readonly studentDingMappingRepo: Repository<StudentDingMapping>,
    private readonly dingTalkService: DingTalkService,
  ) {}

  async syncLeaveStatusForLesson(params: {
    startDate: string;
    endDate: string;
    userIds?: string[];
    autoMatch?: boolean;
  }): Promise<{ synced: number; matched: number; errors: string[] }> {
    const userIds = [...new Set((params.userIds ?? []).filter(Boolean))];
    if (userIds.length === 0) return { synced: 0, matched: 0, errors: [] };
    if (params.startDate > params.endDate) {
      throw new BadRequestException('开始日期不能晚于结束日期');
    }

    const errors: string[] = [];
    let synced = 0;

    for (const date of this.enumerateDates(params.startDate, params.endDate)) {
      for (const userId of userIds) {
        try {
          const leaves = await this.dingTalkService.fetchDailyLeaveStatus(userId, date);
          for (const leave of leaves) {
            await this.upsertLeave(leave);
            synced++;
          }
        } catch (err: unknown) {
          const msg = err instanceof Error ? err.message : String(err);
          errors.push(`请假同步失败 ${userId} ${date}: ${msg}`);
          this.logger.warn(`钉钉请假同步失败 userId=${userId} date=${date}: ${msg}`);
        }
      }
    }

    const matched = params.autoMatch ? await this.autoMatchLeaveRecords() : 0;
    if (synced > 0 || matched > 0) {
      this.logger.log(`钉钉请假同步完成: 新增/更新 ${synced} 条, 匹配 ${matched} 条, 错误 ${errors.length} 条`);
    }
    return { synced, matched, errors };
  }

  private async upsertLeave(result: DingTalkLeaveResult): Promise<void> {
    const existing = await this.dingLeaveRawRepo.findOne({
      where: { dingId: result.procInstId },
    });
    if (existing) {
      Object.assign(existing, {
        dingUserId: result.userId,
        workDate: result.workDate,
        leaveType: result.leaveType,
        tagName: result.tagName,
        startTime: result.beginTime,
        endTime: result.endTime,
        approvedAt: result.approvedAt,
        duration: result.duration,
        durationUnit: result.durationUnit,
        rawData: JSON.stringify(result),
      });
      await this.dingLeaveRawRepo.save(existing);
      return;
    }

    const entity = this.dingLeaveRawRepo.create({
      dingUserId: result.userId,
      userName: await this.resolveStudentName(result.userId),
      workDate: result.workDate,
      dingId: result.procInstId,
      leaveType: result.leaveType,
      tagName: result.tagName,
      startTime: result.beginTime,
      endTime: result.endTime,
      approvedAt: result.approvedAt,
      duration: result.duration,
      durationUnit: result.durationUnit,
      matchStatus: 'unmatched',
      rawData: JSON.stringify(result),
    });
    await this.dingLeaveRawRepo.save(entity);
  }

  /** 通过 dingUserId → StudentDingMapping 自动匹配未匹配的请假记录。 */
  private async autoMatchLeaveRecords(): Promise<number> {
    const unmatched = await this.dingLeaveRawRepo.find({
      where: { matchStatus: 'unmatched' },
    });
    if (unmatched.length === 0) return 0;

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

    let matched = 0;
    const updates: DingLeaveRaw[] = [];
    for (const record of unmatched) {
      const studentId = dingToStudentId.get(record.dingUserId);
      if (studentId == null) continue;
      record.matchedStudentId = studentId;
      record.matchStatus = 'matched';
      updates.push(record);
      matched++;
    }
    if (updates.length > 0) {
      await this.dingLeaveRawRepo.save(updates, { chunk: 50 });
    }
    return matched;
  }

  private enumerateDates(startDate: string, endDate: string): string[] {
    const dates: string[] = [];
    let cursor = this.parseDate(startDate);
    const end = this.parseDate(endDate);
    while (cursor.getTime() <= end.getTime()) {
      dates.push(this.formatDate(cursor));
      cursor = new Date(cursor);
      cursor.setUTCDate(cursor.getUTCDate() + 1);
    }
    return dates;
  }

  private parseDate(value: string): Date {
    const date = new Date(`${value}T00:00:00.000Z`);
    if (Number.isNaN(date.getTime())) {
      throw new BadRequestException(`无效日期: ${value}`);
    }
    return date;
  }

  private formatDate(value: Date): string {
    return value.toISOString().slice(0, 10);
  }

  private async resolveStudentName(dingUserId: string): Promise<string> {
    const mapping = await this.studentDingMappingRepo.findOne({
      where: { dingUserId },
    });
    if (!mapping) return '';
    const student = await this.studentRepo.findOne({ where: { id: mapping.studentId } });
    return student?.name || '';
  }
}
