import { Injectable, Logger } from '@nestjs/common';
import type { Request } from 'express';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { MeetingAttendanceAuditLogWriter } from './audit-log-writer.service';
import {
  MEETING_ATTENDANCE_AUDIT_ACTIONS,
  MEETING_ATTENDANCE_AUDIT_RESOURCES,
} from '../constants/audit';
import { MeetingAttendanceError } from '../errors/meeting-attendance.error';

export interface PtoMarkingResult {
  meetingId: string;
  marked: number;
  details: Array<{
    userId: string;
    previousStatus: string;
    scheduleId: string;
  }>;
}

type Actor = {
  userId?: string;
  id?: string;
  email?: string;
  displayName?: string;
  username?: string;
  roles?: any[];
};

/**
 * 自动 PTO 标记：扫描会议必参会人，凡 ADP PTO 时段与会议时段重叠的，
 * 把 MeetingAttendance.status 设为 PTO 并写审计。
 *
 * 隐私边界：审计 metadata 不写 leaveType / payCode / reason；只写 scheduleId 作为追溯锚点。
 */
@Injectable()
export class MeetingPtoMarkingService {
  private readonly logger = new Logger(MeetingPtoMarkingService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly auditLogWriter: MeetingAttendanceAuditLogWriter,
  ) {}

  /**
   * 对一个会议应用 PTO 标记。
   * @param meetingId 会议 ID
   * @param actor 操作者（手动触发时是用户；自动触发时为 system token）
   * @param request HTTP request，用于审计日志
   */
  async applyForMeeting(
    meetingId: string,
    actor?: Actor,
    request?: Request,
  ): Promise<PtoMarkingResult> {
    // 加载会议 + 必参会人
    const meeting = await this.prisma.meeting.findUnique({
      where: { id: meetingId },
      include: {
        requiredAttendees: {
          select: {
            userId: true,
            user: { select: { id: true, displayName: true, email: true } },
          },
        },
      },
    });
    if (!meeting) {
      throw new MeetingAttendanceError(404, 'Meeting not found');
    }

    const attendeeUserIds = meeting.requiredAttendees.map((r) => r.userId);
    if (attendeeUserIds.length === 0) {
      return { meetingId, marked: 0, details: [] };
    }

    // 找时段重叠的 PTO 记录：start < meeting.end AND end > meeting.start
    const overlapping = await this.prisma.adpPtoSchedule.findMany({
      where: {
        userId: { in: attendeeUserIds },
        startTime: { lt: meeting.endTime },
        endTime: { gt: meeting.startTime },
      },
      select: { id: true, userId: true },
    });

    if (overlapping.length === 0) {
      return { meetingId, marked: 0, details: [] };
    }

    // 按 userId 分组（一人可能命中多条 PTO，标记一次足矣，记其中一条作 basis）
    const byUser = new Map<string, string>(); // userId -> scheduleId
    for (const s of overlapping) {
      if (!byUser.has(s.userId)) byUser.set(s.userId, s.id);
    }

    const details: PtoMarkingResult['details'] = [];

    for (const [userId, scheduleId] of byUser.entries()) {
      // 读取当前 attendance 状态（如有）
      const existing = await this.prisma.meetingAttendance.findUnique({
        where: { userId_meetingId: { userId, meetingId } },
        select: { id: true, status: true },
      });

      // 如果用户已经签到（ON_SITE / ONLINE），不覆盖（人工签到优先）
      if (existing && (existing.status === 'ON_SITE' || existing.status === 'ONLINE')) {
        continue;
      }

      // 已经是 PTO：跳过，不重复审计
      if (existing && existing.status === 'PTO') {
        continue;
      }

      const previousStatus = existing?.status ?? 'NOT_CHECKED_IN';

      // upsert attendance.status = PTO
      await this.prisma.meetingAttendance.upsert({
        where: { userId_meetingId: { userId, meetingId } },
        create: {
          userId,
          meetingId,
          status: 'PTO',
        },
        update: {
          status: 'PTO',
        },
      });

      details.push({ userId, previousStatus, scheduleId });

      // 审计：metadata 仅含 scheduleId，不含 leaveType/payCode
      await this.auditLogWriter.log({
        request,
        actor,
        action: MEETING_ATTENDANCE_AUDIT_ACTIONS.ATTENDANCE_PTO_AUTO_MARKED,
        resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.ATTENDANCE,
        statusCode: 200,
        resourceId: existing?.id,
        changes: {
          meetingId,
          userId,
          previousStatus,
          newStatus: 'PTO',
          basis: { adpPtoScheduleId: scheduleId, source: 'ADP' },
        },
      });
    }

    return { meetingId, marked: details.length, details };
  }

  /**
   * 自动触发版本：吞错+异步执行，绝不影响调用方主流程。
   * MeetingsService.create / update 完成后调用。
   */
  applyForMeetingFireAndForget(meetingId: string): void {
    setImmediate(() => {
      this.applyForMeeting(meetingId).catch((e) => {
        this.logger.warn(`PTO 自动标记失败 meeting=${meetingId}: ${e?.message ?? e}`);
      });
    });
  }
}
