import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { CycleStatus, CycleType, Prisma } from '@prisma/client';
import { PERFORMANCE_ERROR_CODES } from '../constants/error-codes';
import { GRADE_ORDER, GRADE_COLORS, scoreToGradeCode } from '../constants/grade-defaults';
import { BusinessException } from '@common/exceptions/business.exception';

/**
 * 周期管理服务
 * 负责绩效周期的完整生命周期管理
 */
@Injectable()
export class CycleService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * 有效的状态流转规则
   */
  private readonly VALID_TRANSITIONS: Record<CycleStatus, CycleStatus[]> = {
    DRAFT: ['GOAL_SETTING'],
    GOAL_SETTING: ['IN_PROGRESS'],
    IN_PROGRESS: ['EVALUATING'],
    EVALUATING: ['CALIBRATING'],
    CALIBRATING: ['CONFIRMING'],
    CONFIRMING: ['COMPLETED'],
    COMPLETED: ['ARCHIVED'],
    ARCHIVED: [],
  };

  // ==================== CRUD ====================

  /**
   * 创建绩效周期
   */
  async create(data: {
    name: string;
    type: CycleType;
    startDate: Date;
    endDate: Date;
    gradeConfigId?: string;
    parentCycleId?: string;
    organizationId?: string;
    createdBy: string;
  }) {
    // 验证日期逻辑
    this.validateDates(data);

    // 检查日期冲突
    const hasConflict = await this.checkDateConflict(
      data.startDate,
      data.endDate,
      data.type,
      undefined,
      data.organizationId,
    );
    if (hasConflict) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.message,
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.code,
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.httpStatus,
      );
    }

    if (!data.createdBy) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.message,
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.code,
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.httpStatus,
        { field: 'createdBy' },
      );
    }

    // 如果未指定 organizationId，从用户的主组织推断
    if (!data.organizationId) {
      const userDept = await this.prisma.userDepartment.findFirst({
        where: { userId: data.createdBy },
        include: { department: true },
        orderBy: { createdAt: 'asc' },
      });
      if (userDept?.department?.organizationId) {
        data.organizationId = userDept.department.organizationId;
      } else {
        // 回退：取第一个可用组织
        const org = await this.prisma.organization.findFirst({
          where: { isActive: true, deletedAt: null },
        });
        if (org) {
          data.organizationId = org.id;
        }
      }
    }

    if (!data.organizationId) {
      throw new BusinessException(
        '无法确定组织，请提供 organizationId',
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.code,
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.httpStatus,
        { field: 'organizationId' },
      );
    }

    // 如果指定了等级配置，验证其存在性
    if (data.gradeConfigId) {
      const gradeConfig = await this.prisma.gradeConfig.findFirst({
        where: { id: data.gradeConfigId, deletedAt: null },
      });
      if (!gradeConfig) {
        throw new BusinessException(
          PERFORMANCE_ERROR_CODES.COMMON_NOT_FOUND.message,
          PERFORMANCE_ERROR_CODES.COMMON_NOT_FOUND.code,
          PERFORMANCE_ERROR_CODES.COMMON_NOT_FOUND.httpStatus,
        );
      }
    }

    return this.prisma.performanceCycle.create({
      data: {
        name: data.name,
        type: data.type,
        status: 'DRAFT',
        startDate: data.startDate,
        endDate: data.endDate,
        createdBy: data.createdBy,
        organization: { connect: { id: data.organizationId } },
        ...(data.gradeConfigId ? { gradeConfig: { connect: { id: data.gradeConfigId } } } : {}),
        ...(data.parentCycleId ? { parentCycle: { connect: { id: data.parentCycleId } } } : {}),
      },
      include: {
        gradeConfig: true,
      },
    });
  }

  /**
   * 查询周期列表
   */
  async findAll(query: {
    status?: CycleStatus;
    page?: number;
    pageSize?: number;
    organizationId?: string;
  }) {
    const { status, page = 1, pageSize = 20, organizationId } = query;
    const skip = (page - 1) * pageSize;

    const where: Prisma.PerformanceCycleWhereInput = { deletedAt: null };
    if (status) where.status = status;
    if (organizationId) where.organizationId = organizationId;

    const [items, total] = await Promise.all([
      this.prisma.performanceCycle.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          gradeConfig: true,
        },
      }),
      this.prisma.performanceCycle.count({ where }),
    ]);

    return {
      items,
      pagination: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  /**
   * 获取周期详情
   */
  async findById(id: string) {
    const cycle = await this.prisma.performanceCycle.findFirst({
      where: { id, deletedAt: null },
      include: {
        gradeConfig: true,
        kpiAssignments: {
          where: { deletedAt: null },
          take: 5,
          orderBy: { createdAt: 'desc' },
        },
        evaluations: {
          where: { deletedAt: null },
          take: 5,
          orderBy: { createdAt: 'desc' },
        },
      },
    });

    if (!cycle) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_NOT_FOUND.message,
        PERFORMANCE_ERROR_CODES.CYCLE_NOT_FOUND.code,
        PERFORMANCE_ERROR_CODES.CYCLE_NOT_FOUND.httpStatus,
      );
    }

    return cycle;
  }

  /**
   * 更新周期（仅 DRAFT 状态可编辑）
   */
  async update(
    id: string,
    data: {
      name?: string;
      startDate?: Date;
      endDate?: Date;
      gradeConfigId?: string;
    },
  ) {
    const cycle = await this.findById(id);

    if (cycle.status === 'ARCHIVED') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_ARCHIVED_READONLY.message,
        PERFORMANCE_ERROR_CODES.CYCLE_ARCHIVED_READONLY.code,
        PERFORMANCE_ERROR_CODES.CYCLE_ARCHIVED_READONLY.httpStatus,
      );
    }
    if (cycle.status !== 'DRAFT') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    // 如果更新日期，需要重新验证
    const startDate = data.startDate || cycle.startDate;
    const endDate = data.endDate || cycle.endDate;

    if (data.startDate || data.endDate) {
      this.validateDates({
        startDate,
        endDate,
      });

      const hasConflict = await this.checkDateConflict(
        startDate,
        endDate,
        cycle.type,
        id,
        cycle.organizationId,
      );
      if (hasConflict) {
        throw new BusinessException(
          PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.message,
          PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.code,
          PERFORMANCE_ERROR_CODES.CYCLE_DATE_CONFLICT.httpStatus,
        );
      }
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: {
        name: data.name,
        startDate: data.startDate,
        endDate: data.endDate,
        gradeConfigId: data.gradeConfigId,
      },
      include: {
        gradeConfig: true,
      },
    });
  }

  /**
   * 删除周期（仅 DRAFT 状态可删除）
   */
  async delete(id: string) {
    const cycle = await this.findById(id);

    if (cycle.status !== 'DRAFT') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_CANNOT_DELETE.message,
        PERFORMANCE_ERROR_CODES.CYCLE_CANNOT_DELETE.code,
        PERFORMANCE_ERROR_CODES.CYCLE_CANNOT_DELETE.httpStatus,
      );
    }

    await this.prisma.performanceCycle.update({
      where: { id },
      data: { deletedAt: new Date() },
    });

    return { success: true };
  }

  // ==================== 状态流转 ====================

  /**
   * 发布周期 DRAFT → GOAL_SETTING
   */
  async publish(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'GOAL_SETTING')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    // 验证必填字段：等级配置为硬守卫
    if (!cycle.gradeConfigId) {
      // 尝试使用默认等级配置
      const defaultConfig = await this.prisma.gradeConfig.findFirst({
        where: { isDefault: true, isActive: true, deletedAt: null },
      });
      if (defaultConfig) {
        await this.prisma.performanceCycle.update({
          where: { id },
          data: { gradeConfigId: defaultConfig.id },
        });
      } else {
        throw new BusinessException(
          PERFORMANCE_ERROR_CODES.CYCLE_GRADE_CONFIG_REQUIRED.message,
          PERFORMANCE_ERROR_CODES.CYCLE_GRADE_CONFIG_REQUIRED.code,
          PERFORMANCE_ERROR_CODES.CYCLE_GRADE_CONFIG_REQUIRED.httpStatus,
        );
      }
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'GOAL_SETTING' },
      include: { gradeConfig: true },
    });
  }

  /**
   * 开始执行 GOAL_SETTING → IN_PROGRESS
   */
  async startExecution(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'IN_PROGRESS')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'IN_PROGRESS' },
    });
  }

  /**
   * 开始评估 IN_PROGRESS → EVALUATING
   */
  async startEvaluation(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'EVALUATING')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'EVALUATING' },
    });
  }

  /**
   * 开始校准 EVALUATING → CALIBRATING
   * 前置条件：所有评估已完成（KPI、360，如启用）
   */
  async startCalibration(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'CALIBRATING')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    const pendingItems: string[] = [];

    // 检查 KPI 评估完成情况（他评分数已提交）
    const pendingKpiAssessments = await this.prisma.kpiAssessment.count({
      where: {
        assignment: { cycleId: id, deletedAt: null },
        deletedAt: null,
        status: { notIn: ['MANAGER_EVALUATED', 'CONFIRMED'] },
      },
    });
    if (pendingKpiAssessments > 0) {
      pendingItems.push(`${pendingKpiAssessments} 个 KPI 评估未完成他评`);
    }

    // 检查 360 评估完成情况（评估收集已完成）
    const pending360Evaluations = await this.prisma.evaluation360.count({
      where: {
        cycleId: id,
        deletedAt: null,
        status: { not: 'COMPLETED' },
      },
    });
    if (pending360Evaluations > 0) {
      pendingItems.push(`${pending360Evaluations} 个 360 评估未完成`);
    }

    if (pendingItems.length > 0) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_INCOMPLETE_EVALUATIONS.message,
        PERFORMANCE_ERROR_CODES.CYCLE_INCOMPLETE_EVALUATIONS.code,
        PERFORMANCE_ERROR_CODES.CYCLE_INCOMPLETE_EVALUATIONS.httpStatus,
        { pendingItems },
      );
    }

    // 进入校准前自动计算绩效初始结果（校准页面需要展示员工分数和等级分布）
    await this.ensurePerformanceResults(id, cycle);

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'CALIBRATING' },
    });
  }

  /**
   * 完成周期 CONFIRMING → COMPLETED
   */
  async complete(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'COMPLETED')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'COMPLETED' },
    });
  }

  /**
   * 归档周期 COMPLETED → ARCHIVED
   * @param id 周期ID
   */
  async archive(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'ARCHIVED')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'ARCHIVED' },
    });
  }

  /**
   * 开始确认 CALIBRATING → CONFIRMING
   * 发布绩效结果给员工
   */
  async startConfirming(id: string) {
    const cycle = await this.findById(id);

    if (!this.validateStatusTransition(cycle.status, 'CONFIRMING')) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_STATUS_INVALID.httpStatus,
      );
    }

    const now = new Date();

    // 确保 result 已生成（复用公共方法）
    await this.ensurePerformanceResults(id, cycle);

    // 进入 CONFIRMING 阶段时自动发布所有结果，让员工可以查看和确认
    await this.prisma.performanceResult.updateMany({
      where: { cycleId: id, deletedAt: null, isPublished: false },
      data: { isPublished: true, publishedAt: now },
    });

    return this.prisma.performanceCycle.update({
      where: { id },
      data: { status: 'CONFIRMING' },
    });
  }

  // ==================== 进度查询 ====================

  /**
   * 查询目标设定进度
   */
  async getGoalSettingProgress(cycleId: string) {
    const cycle = await this.findById(cycleId);
    const orgId = (cycle as any).organizationId;

    // 以下查询互相独立，并发执行
    const [totalEmployees, statusGroups, deptStats, employeesWithKpi, submittedAssignments, pendingEmployees] =
      await Promise.all([
        this.countOrgEmployees(orgId),
        this.prisma.kpiAssignment.groupBy({
          by: ['status'],
          where: { cycleId, deletedAt: null },
          _count: true,
        }),
        orgId ? this.prisma.$queryRaw<
          Array<{ dept_id: string; dept_name: string; total_employees: bigint; submitted_employees: bigint; draft_employees: bigint }>
        >`
          SELECT
            d.id AS dept_id,
            d.name AS dept_name,
            COUNT(DISTINCT ud.user_id) AS total_employees,
            COUNT(DISTINCT CASE
              WHEN EXISTS (
                SELECT 1 FROM platform_performance.kpi_assignment ka
                WHERE ka.employee_id = ud.user_id AND ka.cycle_id = ${cycleId}::uuid
                  AND ka.deleted_at IS NULL AND ka.assignment_status IN ('SUBMITTED', 'APPROVED')
              ) THEN ud.user_id
            END) AS submitted_employees,
            COUNT(DISTINCT CASE
              WHEN EXISTS (
                SELECT 1 FROM platform_performance.kpi_assignment ka
                WHERE ka.employee_id = ud.user_id AND ka.cycle_id = ${cycleId}::uuid
                  AND ka.deleted_at IS NULL AND ka.assignment_status = 'DRAFT'
              ) THEN ud.user_id
            END) AS draft_employees
          FROM corp_hr.user_departments ud
          JOIN corp_hr.departments d ON d.id = ud.department_id
          WHERE ud.organization_id = ${orgId}::uuid
            AND ud.left_at IS NULL
            AND d.parent_id IS NOT NULL
            AND d.deleted_at IS NULL
          GROUP BY d.id, d.name
          ORDER BY d.name
        ` : Promise.resolve([] as Array<{ dept_id: string; dept_name: string; total_employees: bigint; submitted_employees: bigint; draft_employees: bigint }>),
        this.prisma.kpiAssignment.groupBy({
          by: ['employeeId'],
          where: { cycleId, deletedAt: null },
        }),
        this.prisma.kpiAssignment.findMany({
          where: { cycleId, deletedAt: null, status: { in: ['SUBMITTED', 'APPROVED'] } },
          select: { employeeId: true },
        }),
        orgId ? this.prisma.$queryRaw<
          Array<{ employee_id: string; employee_name: string; department_name: string }>
        >`
          SELECT
            u.id AS employee_id,
            COALESCE(u.display_name, u.username) AS employee_name,
            d.name AS department_name
          FROM corp_hr.user_departments ud
          JOIN platform_iam.users u ON u.id = ud.user_id
          JOIN corp_hr.departments d ON d.id = ud.department_id
          WHERE ud.organization_id = ${orgId}::uuid
            AND ud.left_at IS NULL
            AND d.parent_id IS NOT NULL
            AND d.deleted_at IS NULL
            AND NOT EXISTS (
              SELECT 1 FROM platform_performance.kpi_assignment ka
              WHERE ka.employee_id = ud.user_id AND ka.cycle_id = ${cycleId}::uuid
                AND ka.deleted_at IS NULL AND ka.assignment_status IN ('SUBMITTED', 'APPROVED')
            )
          ORDER BY d.name, u.display_name
        ` : Promise.resolve([] as Array<{ employee_id: string; employee_name: string; department_name: string }>),
      ]);

    const total = statusGroups.reduce((sum, g) => sum + g._count, 0);
    const submitted = statusGroups
      .filter((g) => ['SUBMITTED', 'APPROVED'].includes(g.status))
      .reduce((sum, g) => sum + g._count, 0);
    const draft = statusGroups
      .filter((g) => g.status === 'DRAFT')
      .reduce((sum, g) => sum + g._count, 0);
    const notStarted = 0;

    const departments = deptStats.map((d) => ({
      id: d.dept_id,
      name: d.dept_name,
      total: Number(d.total_employees),
      submitted: Number(d.submitted_employees),
      draft: Number(d.draft_employees),
      notStarted: Number(d.total_employees) - Number(d.submitted_employees) - Number(d.draft_employees),
    }));

    const submittedEmployees = new Set(submittedAssignments.map((a) => a.employeeId));

    return {
      totalEmployees,
      submittedCount: submittedEmployees.size,
      draftCount: employeesWithKpi.length - submittedEmployees.size,
      notStartedCount: totalEmployees - employeesWithKpi.length,
      total, submitted, draft, notStarted, departments,
      pendingEmployees: pendingEmployees.map((e) => ({
        employeeId: e.employee_id,
        employeeName: e.employee_name,
        departmentName: e.department_name,
      })),
    };
  }

  /**
   * 查询评估进度
   */
  async getEvaluationProgress(cycleId: string) {
    const cycle = await this.findById(cycleId);
    const orgId = (cycle as any).organizationId;

    // 以下查询全部独立，并发执行
    const [
      totalEmployeesRaw, kpiStatusGroups, e360Completed,
      pendingAssessments, deptEvalStats,
      allEmployeeIds, evalAssessments, allEmployees, e360CompletedList,
    ] = await Promise.all([
      this.countOrgEmployees(orgId),
      this.prisma.kpiAssessment.groupBy({
        by: ['status'],
        where: { assignment: { cycleId, deletedAt: null }, deletedAt: null },
        _count: true,
      }),
      this.prisma.evaluation360.count({
        where: { cycleId, deletedAt: null, status: 'COMPLETED' },
      }),
      this.prisma.kpiAssessment.findMany({
        where: {
          assignment: { cycleId, deletedAt: null },
          deletedAt: null,
          status: { notIn: ['MANAGER_EVALUATED', 'CONFIRMED'] },
        },
        select: { assignment: { select: { employeeId: true } } },
      }),
      orgId ? this.prisma.$queryRaw<
        Array<{ dept_id: string; dept_name: string; total_employees: bigint; self_evaluated: bigint; manager_evaluated: bigint; e360_completed: bigint }>
      >`
        SELECT
          d.id AS dept_id,
          d.name AS dept_name,
          COUNT(DISTINCT ud.user_id) AS total_employees,
          COUNT(DISTINCT CASE
            WHEN EXISTS (
              SELECT 1 FROM platform_performance.kpi_assessment ka2
              JOIN platform_performance.kpi_assignment ka ON ka.id = ka2.assignment_id
              WHERE ka.employee_id = ud.user_id AND ka.cycle_id = ${cycleId}::uuid
                AND ka.deleted_at IS NULL AND ka2.deleted_at IS NULL
                AND ka2.status IN ('SELF_EVALUATED', 'MANAGER_EVALUATED', 'CONFIRMED')
            ) THEN ud.user_id
          END) AS self_evaluated,
          COUNT(DISTINCT CASE
            WHEN EXISTS (
              SELECT 1 FROM platform_performance.kpi_assessment ka2
              JOIN platform_performance.kpi_assignment ka ON ka.id = ka2.assignment_id
              WHERE ka.employee_id = ud.user_id AND ka.cycle_id = ${cycleId}::uuid
                AND ka.deleted_at IS NULL AND ka2.deleted_at IS NULL
                AND ka2.status IN ('MANAGER_EVALUATED', 'CONFIRMED')
            ) THEN ud.user_id
          END) AS manager_evaluated,
          COUNT(DISTINCT CASE
            WHEN EXISTS (
              SELECT 1 FROM platform_performance.evaluation_360 e360
              WHERE e360.target_id = ud.user_id AND e360.cycle_id = ${cycleId}::uuid
                AND e360.deleted_at IS NULL AND e360.status = 'COMPLETED'
            ) THEN ud.user_id
          END) AS e360_completed
        FROM corp_hr.user_departments ud
        JOIN corp_hr.departments d ON d.id = ud.department_id
        WHERE ud.organization_id = ${orgId}::uuid
          AND ud.left_at IS NULL
          AND d.parent_id IS NOT NULL
          AND d.deleted_at IS NULL
        GROUP BY d.id, d.name
        ORDER BY d.name
      ` : Promise.resolve([] as Array<{ dept_id: string; dept_name: string; total_employees: bigint; self_evaluated: bigint; manager_evaluated: bigint; e360_completed: bigint }>),
      orgId ? this.prisma.$queryRaw<Array<{ user_id: string }>>`
        SELECT DISTINCT ud.user_id FROM corp_hr.user_departments ud
        JOIN corp_hr.departments d ON d.id = ud.department_id
        WHERE ud.organization_id = ${orgId}::uuid AND ud.left_at IS NULL
          AND d.parent_id IS NOT NULL AND d.deleted_at IS NULL
      ` : Promise.resolve([] as Array<{ user_id: string }>),
      this.prisma.kpiAssessment.findMany({
        where: { assignment: { cycleId, deletedAt: null }, deletedAt: null, status: { in: ['SELF_EVALUATED', 'MANAGER_EVALUATED', 'CONFIRMED'] } },
        select: { status: true, assignment: { select: { employeeId: true } } },
      }),
      orgId ? this.prisma.$queryRaw<
        Array<{ employee_id: string; employee_name: string; department_name: string }>
      >`
        SELECT DISTINCT ON (ud.user_id)
          ud.user_id AS employee_id,
          COALESCE(u.display_name, u.username) AS employee_name,
          d.name AS department_name
        FROM corp_hr.user_departments ud
        JOIN platform_iam.users u ON u.id = ud.user_id
        JOIN corp_hr.departments d ON d.id = ud.department_id
        WHERE ud.organization_id = ${orgId}::uuid AND ud.left_at IS NULL
          AND d.parent_id IS NOT NULL AND d.deleted_at IS NULL
        ORDER BY ud.user_id, ud.is_primary DESC
      ` : Promise.resolve([] as Array<{ employee_id: string; employee_name: string; department_name: string }>),
      this.prisma.evaluation360.findMany({
        where: { cycleId, deletedAt: null, status: 'COMPLETED' },
        select: { targetId: true },
      }),
    ]);

    const totalEmployees = totalEmployeesRaw;

    const total = kpiStatusGroups.reduce((sum, g) => sum + g._count, 0);
    const selfEvaluated = kpiStatusGroups
      .filter((g) => ['SELF_EVALUATED', 'MANAGER_EVALUATED', 'CONFIRMED'].includes(g.status))
      .reduce((sum, g) => sum + g._count, 0);
    const managerEvaluated = kpiStatusGroups
      .filter((g) => ['MANAGER_EVALUATED', 'CONFIRMED'].includes(g.status))
      .reduce((sum, g) => sum + g._count, 0);

    const pendingEmployeeIds = [...new Set(pendingAssessments.map((a) => a.assignment.employeeId))];
    const pendingUsers = pendingEmployeeIds.length > 0
      ? await this.prisma.user.findMany({
          where: { id: { in: pendingEmployeeIds } },
          select: { id: true, displayName: true },
        })
      : [];

    const departments = deptEvalStats.map((d) => ({
      id: d.dept_id,
      name: d.dept_name,
      total: Number(d.total_employees),
      selfEvaluated: Number(d.self_evaluated),
      managerEvaluated: Number(d.manager_evaluated),
      e360Completed: Number(d.e360_completed),
    }));

    const selfEvalEmployees = new Set(evalAssessments.map((a) => a.assignment.employeeId));
    const mgrEvalEmployees = new Set(
      evalAssessments
        .filter((a) => a.status === 'MANAGER_EVALUATED' || a.status === 'CONFIRMED')
        .map((a) => a.assignment.employeeId),
    );

    const e360CompletedEmployees = new Set(e360CompletedList.map((e) => e.targetId));

    // 构建 pendingEmployees（未完成自评、未完成主管评、或360未完成的人）
    const pendingEmployees = allEmployees
      .filter((e) => !selfEvalEmployees.has(e.employee_id) || !mgrEvalEmployees.has(e.employee_id) || !e360CompletedEmployees.has(e.employee_id))
      .map((e) => {
        const hasSelf = selfEvalEmployees.has(e.employee_id);
        const hasMgr = mgrEvalEmployees.has(e.employee_id);
        const pendingType = !hasSelf && !hasMgr ? 'both' : !hasSelf ? 'self' : 'manager';
        return {
          id: e.employee_id,
          name: e.employee_name,
          employeeId: e.employee_id,
          employeeName: e.employee_name,
          departmentName: e.department_name,
          pendingType,
          e360Pending: !e360CompletedEmployees.has(e.employee_id),
        };
      });

    return {
      totalEmployees: allEmployeeIds.length,
      selfEvaluated: selfEvalEmployees.size,
      managerEvaluated: mgrEvalEmployees.size,
      e360Total: allEmployeeIds.length,
      e360Completed,
      departments,
      pendingEmployees,
    };
  }

  /**
   * 查询校准进度
   */
  async getCalibrationProgress(cycleId: string) {
    const cycle = await this.findById(cycleId);
    const orgId = (cycle as any).organizationId;

    // totalEmployees 和 results 互相独立，并发查询
    const [totalEmployees, results] = await Promise.all([
      this.countOrgEmployees(orgId),
      this.prisma.performanceResult.findMany({
        where: { cycleId, deletedAt: null, totalScore: { not: null } },
        select: { id: true, gradeCode: true, employeeId: true, totalScore: true },
      }),
    ]);

    // 如果没有正式结果，从 KPI 评估计算临时结果
    type CalResult = { employeeId: string; gradeCode: string | null; score: number };
    let calcResults: CalResult[] = [];
    if (results.length === 0) {
      // 按员工汇总主管评分
      const assessments = await this.prisma.kpiAssessment.findMany({
        where: { assignment: { cycleId, deletedAt: null }, deletedAt: null, status: { in: ['MANAGER_EVALUATED', 'CONFIRMED'] } },
        include: { assignment: { select: { employeeId: true, weight: true } } },
      });
      const byEmp = new Map<string, { totalScore: number; totalWeight: number }>();
      for (const a of assessments) {
        const emp = byEmp.get(a.assignment.employeeId) || { totalScore: 0, totalWeight: 0 };
        const w = Number(a.assignment.weight) || 0;
        emp.totalScore += (Number(a.managerScore) || 0) * w;
        emp.totalWeight += w;
        byEmp.set(a.assignment.employeeId, emp);
      }
      calcResults = Array.from(byEmp.entries()).map(([empId, data]) => {
        const score = data.totalWeight > 0 ? Math.round(data.totalScore / data.totalWeight * 10) / 10 : 0;
        const grade = scoreToGradeCode(score);
        return { employeeId: empId, gradeCode: grade, score };
      });
    }

    const allResults = results.length > 0
      ? results.map((r) => ({ employeeId: r.employeeId, gradeCode: r.gradeCode, score: Number(r.totalScore) || 0 }))
      : calcResults;

    const total = allResults.length;
    const calibrated = results.length > 0
      ? results.filter((r) => r.gradeCode !== null && r.gradeCode !== '').length
      : 0; // KPI 计算的结果还没正式校准

    // 等级分布
    const gradeMap = new Map<string, number>();
    for (const r of allResults) {
      if (r.gradeCode) {
        gradeMap.set(r.gradeCode, (gradeMap.get(r.gradeCode) || 0) + 1);
      }
    }
    const gradeOrder = ['S', 'A', 'B', 'C', 'D'];
    const gradeDistribution = gradeOrder
      .map((code) => ({ grade: code, count: gradeMap.get(code) || 0 }));

    // 按部门统计
    const empIds = allResults.map((r) => r.employeeId);

    // 部门映射和员工姓名并发查询
    const empDeptMap = new Map<string, string>();
    const empNameMap = new Map<string, string>();
    if (empIds.length > 0) {
      const [udList, users] = await Promise.all([
        (this.prisma as any).userDepartment.findMany({
          where: { userId: { in: empIds }, ...(orgId ? { organizationId: orgId } : {}), leftAt: null },
          include: { department: { select: { name: true } } },
        }),
        this.prisma.user.findMany({
          where: { id: { in: empIds } },
          select: { id: true, displayName: true, username: true },
        }),
      ]);
      for (const ud of udList) {
        if (ud.department?.name && !empDeptMap.has(ud.userId)) {
          empDeptMap.set(ud.userId, ud.department.name);
        }
      }
      for (const u of users) empNameMap.set(u.id, u.displayName || u.username);
    }
    const deptEmpGrades = new Map<string, CalResult[]>();
    for (const r of allResults) {
      const deptName = empDeptMap.get(r.employeeId);
      if (deptName) {
        if (!deptEmpGrades.has(deptName)) deptEmpGrades.set(deptName, []);
        deptEmpGrades.get(deptName)!.push(r);
      }
    }

    const departments = Array.from(deptEmpGrades.entries())
      .filter(([, emps]) => emps.length > 0)
      .map(([name, emps]) => ({
        name,
        total: emps.length,
        calibrated: results.length > 0 ? emps.filter((e) => e.gradeCode).length : 0,
        pendingEmployees: emps.map((e) => ({
          id: e.employeeId,
          name: '',
          employeeId: e.employeeId,
          employeeName: '',
          departmentName: name,
          currentGrade: e.gradeCode || '',
          avgScore: e.score,
        })),
      }));
    for (const dept of departments) {
      for (const emp of dept.pendingEmployees) {
        emp.name = empNameMap.get(emp.employeeId) || '?';
        emp.employeeName = emp.name;
      }
    }

    return { totalEmployees, total, calibrated, gradeDistribution, departments };
  }

  /**
   * 查询周期结果汇总
   */
  async getCycleResultsSummary(cycleId: string) {
    const cycle = await this.findById(cycleId);
    const orgId = (cycle as any).organizationId;

    // 以下查询互相独立，并发执行
    const [totalEmployees, results, selfEvalDone, e360Total, e360Done] = await Promise.all([
      this.countOrgEmployees(orgId),
      this.prisma.performanceResult.findMany({
        where: { cycleId, deletedAt: null, totalScore: { not: null } },
        select: {
          employeeId: true, totalScore: true, gradeCode: true,
          confirmStatus: true, viewedByEmployee: true,
        },
      }),
      this.prisma.kpiAssessment.groupBy({
        by: ['status'],
        where: { assignment: { cycleId, deletedAt: null }, deletedAt: null },
        _count: true,
      }),
      this.prisma.evaluation360.count({ where: { cycleId, deletedAt: null } }),
      this.prisma.evaluation360.count({ where: { cycleId, deletedAt: null, status: 'COMPLETED' } }),
    ]);

    const total = results.length;

    if (total === 0) {
      return {
        total: 0,
        avgScore: 0,
        gradeDistribution: [],
        confirmed: 0,
        appealed: 0,
      };
    }

    // 平均分
    const totalScore = results.reduce((sum, r) => sum + Number(r.totalScore), 0);
    const avgScore = Number((totalScore / total).toFixed(2));

    // 等级分布
    const gradeMap = new Map<string, number>();
    for (const result of results) {
      const gc = result.gradeCode;
      if (gc) gradeMap.set(gc, (gradeMap.get(gc) || 0) + 1);
    }
    const gradeDistribution = GRADE_ORDER.map((code) => ({
      grade: code,
      code,
      name: code,
      count: gradeMap.get(code) || 0,
      percentage: total > 0 ? Number((((gradeMap.get(code) || 0) / total) * 100).toFixed(1)) : 0,
      color: GRADE_COLORS[code] || '#646a73',
    }));

    // 确认/申诉数
    const confirmed = results.filter((r) => r.confirmStatus === 'CONFIRMED').length;
    const appealed = results.filter((r) => r.confirmStatus === 'APPEALED').length;
    const viewed = results.filter((r) => r.viewedByEmployee).length;

    const selfEvalCount = selfEvalDone.filter((g) => ['SELF_EVALUATED', 'MANAGER_EVALUATED', 'CONFIRMED'].includes(g.status)).reduce((s, g) => s + g._count, 0);
    const mgrEvalCount = selfEvalDone.filter((g) => ['MANAGER_EVALUATED', 'CONFIRMED'].includes(g.status)).reduce((s, g) => s + g._count, 0);
    const totalAssessments = selfEvalDone.reduce((s, g) => s + g._count, 0);

    const completionStats = {
      selfEval: { completed: selfEvalCount, total: totalAssessments },
      managerEval: { completed: mgrEvalCount, total: totalAssessments },
      e360: { completed: e360Done, total: e360Total || totalEmployees },
      calibration: { completed: confirmed, total },
    };

    // 按部门统计确认进度（部门映射和员工姓名并发查询）
    const employeeIds = results.map((r) => r.employeeId);
    const [userDepts, users] = employeeIds.length > 0
      ? await Promise.all([
          (this.prisma as any).userDepartment.findMany({
            where: { userId: { in: employeeIds }, ...(orgId ? { organizationId: orgId } : {}), leftAt: null },
            include: { department: { select: { id: true, name: true } } },
          }),
          this.prisma.user.findMany({
            where: { id: { in: employeeIds } },
            select: { id: true, displayName: true, username: true },
          }),
        ])
      : [[], []];
    const empDeptMap = new Map<string, string>();
    for (const ud of userDepts) {
      if (ud.department?.name && !empDeptMap.has(ud.userId)) {
        empDeptMap.set(ud.userId, ud.department.name);
      }
    }
    const userNameMap = new Map(users.map((u: any) => [u.id, u.displayName || u.username]));

    // 按部门分组
    const deptResultMap = new Map<string, typeof results>();
    for (const r of results) {
      const deptName = empDeptMap.get(r.employeeId) || '未分配';
      if (!deptResultMap.has(deptName)) deptResultMap.set(deptName, []);
      deptResultMap.get(deptName)!.push(r);
    }

    const departments = Array.from(deptResultMap.entries()).map(([name, deptResults]) => {
      const confirmedCount = deptResults.filter((r) => r.confirmStatus === 'CONFIRMED').length;
      const deptTotalScore = deptResults.reduce((s, r) => s + Number(r.totalScore), 0);
      const deptAvgScore = deptResults.length > 0 ? Math.round(deptTotalScore / deptResults.length * 10) / 10 : 0;
      // 部门内等级分布
      const deptGradeMap = new Map<string, number>();
      for (const r of deptResults) { if (r.gradeCode) deptGradeMap.set(r.gradeCode, (deptGradeMap.get(r.gradeCode) || 0) + 1); }
      const grades = GRADE_ORDER.map((code) => ({ code, name: code, count: deptGradeMap.get(code) || 0, color: GRADE_COLORS[code] || '#646a73' }));

      return {
        name,
        total: deptResults.length,
        confirmed: confirmedCount,
        avgScore: deptAvgScore,
        grades,
        pendingEmployees: deptResults
          .filter((r) => r.confirmStatus !== 'CONFIRMED')
          .map((r) => ({
            employeeId: r.employeeId,
            employeeName: userNameMap.get(r.employeeId) || '?',
            departmentName: name,
            gradeCode: r.gradeCode,
            totalScore: Number(r.totalScore),
            confirmStatus: r.confirmStatus || 'PENDING',
            viewedByEmployee: r.viewedByEmployee,
          })),
      };
    }).sort((a, b) => a.name.localeCompare(b.name));

    return { totalEmployees, total, avgScore, gradeDistribution, confirmed, appealed, viewed, completionStats, departments };
  }

  // ==================== 辅助方法 ====================

  /**
   * 获取周期统计数据
   */
  async getStatistics(id: string) {
    await this.findById(id);

    const assignmentWhere = { cycleId: id, deletedAt: null };
    const assessmentBase = { assignment: assignmentWhere, deletedAt: null };

    const [kpiAssignments, kpiSelfEvaluated, kpiManagerEvaluated, kpiConfirmed, kpiAvgScore, e360Stats] =
      await Promise.all([
        this.prisma.kpiAssignment.count({ where: assignmentWhere }),
        this.prisma.kpiAssessment.count({ where: { ...assessmentBase, selfScore: { not: null } } }),
        this.prisma.kpiAssessment.count({ where: { ...assessmentBase, managerScore: { not: null } } }),
        this.prisma.kpiAssessment.count({ where: { ...assessmentBase, status: 'CONFIRMED' } }),
        this.prisma.kpiAssessment.aggregate({ where: { ...assessmentBase, finalScore: { not: null } }, _avg: { finalScore: true } }),
        this.prisma.evaluation360.groupBy({ by: ['status'], where: { cycleId: id, deletedAt: null }, _count: true }),
      ]);

    return {
      kpi: {
        total: kpiAssignments,
        selfEvaluated: kpiSelfEvaluated,
        managerEvaluated: kpiManagerEvaluated,
        confirmed: kpiConfirmed,
        avgScore: Number(kpiAvgScore._avg?.finalScore) || 0,
      },
      e360: {
        total: e360Stats.reduce((sum, s) => sum + s._count, 0),
        completed: e360Stats.find((s) => s.status === 'COMPLETED')?._count || 0,
      },
    };
  }

  /**
   * 验证状态流转
   */
  /**
   * 确保绩效结果已生成（幂等：已有则跳过）
   * 从 KPI 评估中汇总加权分数 → 匹配等级 → 写入 performance_result
   */
  /** 查询组织下的在职员工总数 */
  private countOrgEmployees(orgId?: string): Promise<number> {
    if (!orgId) return Promise.resolve(0);
    return (this.prisma as any).userDepartment.count({ where: { organizationId: orgId, leftAt: null } });
  }

  private async ensurePerformanceResults(cycleId: string, cycle: any) {
    // 检查是否已有**有效**的 result（totalScore 不为 null），空记录不算
    const validResults = await this.prisma.performanceResult.count({
      where: { cycleId, deletedAt: null, totalScore: { not: null } },
    });
    if (validResults > 0) return;

    // 清理可能存在的空记录
    await this.prisma.performanceResult.deleteMany({
      where: { cycleId, deletedAt: null, totalScore: null },
    });

    const gradeConfigId = cycle.gradeConfigId;
    const gradeConfig = cycle.gradeConfig || (gradeConfigId ? await this.prisma.gradeConfig.findFirst({
      where: { id: gradeConfigId, deletedAt: null },
    }) : null);
    const grades = ((gradeConfig?.grades as any[]) || []);
    const sortedGrades = [...grades].sort((a: any, b: any) => (b.minScore || 0) - (a.minScore || 0));

    const assessments = await this.prisma.kpiAssessment.findMany({
      where: {
        assignment: { cycleId, deletedAt: null },
        status: { in: ['MANAGER_EVALUATED', 'CONFIRMED'] },
        deletedAt: null,
      },
      include: { assignment: { select: { employeeId: true, weight: true } } },
    });

    const employeeScores = new Map<string, { totalWeightedScore: number; totalWeight: number }>();
    for (const a of assessments) {
      const empId = a.assignment.employeeId;
      const score = Number(a.finalScore) || Number(a.managerScore) || 0;
      const weight = Number(a.assignment.weight) || 0;
      const entry = employeeScores.get(empId) || { totalWeightedScore: 0, totalWeight: 0 };
      entry.totalWeightedScore += score * weight;
      entry.totalWeight += weight;
      employeeScores.set(empId, entry);
    }

    if (employeeScores.size === 0) return;

    // 构建批量数据
    const rows: Array<{
      employeeId: string; kpiScore: number; totalScore: number;
      gradeCode: string; gradeName: string;
    }> = [];
    for (const [employeeId, data] of employeeScores) {
      const totalScore = data.totalWeight > 0 ? data.totalWeightedScore / data.totalWeight : 0;
      const grade = sortedGrades.find((g: any) => totalScore >= (g.minScore || 0) && totalScore <= (g.maxScore || 100));
      rows.push({
        employeeId,
        kpiScore: totalScore,
        totalScore,
        gradeCode: grade?.code || 'B',
        gradeName: grade?.name || '良好',
      });
    }

    // 使用 Prisma $transaction + createMany 批量写入，避免 SQL 注入风险
    await this.prisma.$transaction(async (tx) => {
      // 先删除已有记录（ON CONFLICT 替代方案），然后 createMany
      await tx.performanceResult.deleteMany({
        where: { cycleId, employeeId: { in: rows.map((r) => r.employeeId) } },
      });
      await tx.performanceResult.createMany({
        data: rows.map((r) => ({
          cycleId,
          employeeId: r.employeeId,
          organizationId: cycle.organizationId,
          kpiScore: r.kpiScore,
          kpiWeight: 100,
          totalScore: r.totalScore,
          proposedGradeCode: r.gradeCode,
          proposedGradeName: r.gradeName,
          gradeCode: r.gradeCode,
          gradeName: r.gradeName,
          isPublished: false,
        })),
      });
    });
  }

  validateStatusTransition(from: CycleStatus, to: CycleStatus): boolean {
    return this.VALID_TRANSITIONS[from]?.includes(to) ?? false;
  }

  /**
   * 检查日期冲突
   */
  async checkDateConflict(
    startDate: Date,
    endDate: Date,
    type: CycleType,
    excludeId?: string,
    organizationId?: string,
  ): Promise<boolean> {
    const where: Prisma.PerformanceCycleWhereInput = {
      type,
      deletedAt: null,
      status: { not: 'ARCHIVED' },
      OR: [
        {
          startDate: { lte: endDate },
          endDate: { gte: startDate },
        },
      ],
    };

    if (excludeId) {
      where.id = { not: excludeId };
    }

    if (organizationId) {
      where.organizationId = organizationId;
    }

    const conflictCount = await this.prisma.performanceCycle.count({ where });
    return conflictCount > 0;
  }

  /**
   * 验证日期逻辑
   */
  private validateDates(data: {
    startDate: Date;
    endDate: Date;
  }) {
    const { startDate, endDate } = data;

    if (startDate >= endDate) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_INVALID.message,
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_INVALID.code,
        PERFORMANCE_ERROR_CODES.CYCLE_DATE_INVALID.httpStatus,
      );
    }
  }

  /**
   * 获取当前活跃周期
   */
  async findActiveCycle(organizationId?: string) {
    return this.prisma.performanceCycle.findFirst({
      where: {
        deletedAt: null,
        status: { in: ['GOAL_SETTING', 'IN_PROGRESS', 'EVALUATING', 'CALIBRATING', 'CONFIRMING'] },
        ...(organizationId ? { organizationId } : {}),
      },
      orderBy: { startDate: 'desc' },
      include: { gradeConfig: true },
    });
  }

  /**
   * 批量获取周期（用于下拉选择）
   */
  async findForSelect(includeArchived = false, organizationId?: string) {
    const where: Prisma.PerformanceCycleWhereInput = { deletedAt: null };
    if (!includeArchived) {
      where.status = { not: 'ARCHIVED' };
    }
    if (organizationId) where.organizationId = organizationId;

    return this.prisma.performanceCycle.findMany({
      where,
      select: {
        id: true,
        name: true,
        type: true,
        status: true,
        startDate: true,
        endDate: true,
      },
      orderBy: { startDate: 'desc' },
    });
  }
}
