import { Injectable, BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { NotificationChannel, Prisma } from '@prisma/client';
import { GradeConfigService, GradeDefinition } from './grade-config.service';
import { PERFORMANCE_ERROR_CODES } from '../constants/error-codes';
import * as XLSX from 'xlsx';
import { NotificationService } from '@core/messaging/notification/notification.service';
import { BusinessException } from '@common/exceptions/business.exception';
import { SaveOverallCommentDto } from '../dto/result.dto';

/**
 * 绩效结果服务
 * 负责结果计算、发布、员工查看
 */
@Injectable()
export class ResultService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly gradeConfigService: GradeConfigService,
    private readonly notificationService: NotificationService,
  ) {}

  // ==================== 结果计算 ====================

  async calculateResults(
    cycleId: string,
    options: {
      weights: { kpi: number; e360: number };
      employeeIds?: string[];
    },
  ) {
    const cycle = await this.prisma.performanceCycle.findFirst({
      where: { id: cycleId, deletedAt: null },
      include: { gradeConfig: true },
    });

    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,
      );
    }

    const weightTotal = options.weights.kpi + options.weights.e360;
    if (Math.abs(weightTotal - 100) > 0.01) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_WEIGHT_INVALID.message,
        PERFORMANCE_ERROR_CODES.RESULT_WEIGHT_INVALID.code,
        PERFORMANCE_ERROR_CODES.RESULT_WEIGHT_INVALID.httpStatus,
      );
    }

    // 获取等级配置
    const gradeConfig = cycle.gradeConfig;
    const grades = (gradeConfig?.grades as unknown as GradeDefinition[]) ||
      GradeConfigService.DEFAULT_GRADES;

    // 获取周期内的 KPI 分配员工
    const assignmentEmployees = await this.prisma.kpiAssignment.findMany({
      where: { cycleId, deletedAt: null },
      select: { employeeId: true },
    });

    const evaluationTargets = await this.prisma.evaluation360.findMany({
      where: { cycleId, deletedAt: null },
      select: { targetId: true },
    });

    const allEmployeeIds = new Set<string>([
      ...assignmentEmployees.map((a) => a.employeeId),
      ...evaluationTargets.map((e) => e.targetId),
    ]);

    const targetEmployeeIds = options.employeeIds?.length
      ? options.employeeIds.filter((id) => allEmployeeIds.has(id))
      : Array.from(allEmployeeIds);

    if (targetEmployeeIds.length === 0) {
      return { cycleId, calculated: 0, failed: 0, failedEmployees: [] };
    }

    // 获取所有员工的 KPI 考核
    const kpiAssessments = await this.prisma.kpiAssessment.findMany({
      where: {
        assignment: { employeeId: { in: targetEmployeeIds }, cycleId, deletedAt: null },
        deletedAt: null,
      },
      include: {
        assignment: true,
      },
    });

    if (options.weights.kpi > 0) {
      const hasPending = kpiAssessments.some(
        (assessment) => !['MANAGER_EVALUATED', 'CONFIRMED'].includes(assessment.status),
      );
      if (hasPending) {
        throw new BusinessException(
          PERFORMANCE_ERROR_CODES.RESULT_KPI_INCOMPLETE.message,
          PERFORMANCE_ERROR_CODES.RESULT_KPI_INCOMPLETE.code,
          PERFORMANCE_ERROR_CODES.RESULT_KPI_INCOMPLETE.httpStatus,
        );
      }
    }

    // 按员工分组 KPI 分数（仅已完成他评/确认的记录）
    const employeeKpiScores = new Map<string, { totalScore: number; totalWeight: number }>();
    for (const assessment of kpiAssessments) {
      if (!['MANAGER_EVALUATED', 'CONFIRMED'].includes(assessment.status)) {
        continue;
      }
      const employeeId = assessment.assignment.employeeId;
      const score = Number(assessment.finalScore) || Number(assessment.managerScore) || 0;
      const weight = Number(assessment.assignment.weight) || 0;

      if (!employeeKpiScores.has(employeeId)) {
        employeeKpiScores.set(employeeId, { totalScore: 0, totalWeight: 0 });
      }
      const entry = employeeKpiScores.get(employeeId)!;
      entry.totalScore += score * weight;
      entry.totalWeight += weight;
    }

    // 获取 360 评估分数
    const evaluations360 = await this.prisma.evaluation360.findMany({
      where: { cycleId, targetId: { in: targetEmployeeIds }, deletedAt: null },
      include: {
        tasks: {
          where: { status: 'SUBMITTED', deletedAt: null },
          include: { responses: { where: { deletedAt: null } } },
        },
      },
    });

    // 按员工分组累加 360 分数
    const employee360Data = new Map<string, { totalScore: number; count: number }>();
    for (const evaluation of evaluations360) {
      if (evaluation.status !== 'COMPLETED') {
        continue;
      }
      const totalScore = evaluation.tasks.reduce((sum, task) => {
        const taskAvg = task.responses.length > 0
          ? task.responses.reduce((s, r) => s + Number(r.score), 0) / task.responses.length
          : 0;
        return sum + taskAvg;
      }, 0);
      const avgScore = evaluation.tasks.length > 0 ? totalScore / evaluation.tasks.length : 0;

      const data = employee360Data.get(evaluation.targetId) || { totalScore: 0, count: 0 };
      data.totalScore += avgScore;
      data.count++;
      employee360Data.set(evaluation.targetId, data);
    }
    // 计算平均分数
    const employee360Scores = new Map<string, number>();
    for (const [employeeId, data] of employee360Data) {
      employee360Scores.set(employeeId, data.count > 0 ? data.totalScore / data.count : 0);
    }

    const failedEmployees: Array<{ employeeId: string; reason: string }> = [];
    let calculated = 0;

    const kpiPendingCountByEmployee = new Map<string, number>();
    for (const assessment of kpiAssessments) {
      if (!['MANAGER_EVALUATED', 'CONFIRMED'].includes(assessment.status)) {
        const count = kpiPendingCountByEmployee.get(assessment.assignment.employeeId) || 0;
        kpiPendingCountByEmployee.set(assessment.assignment.employeeId, count + 1);
      }
    }

    const e360PendingCountByEmployee = new Map<string, number>();
    for (const evaluation of evaluations360) {
      if (evaluation.status !== 'COMPLETED') {
        const count = e360PendingCountByEmployee.get(evaluation.targetId) || 0;
        e360PendingCountByEmployee.set(evaluation.targetId, count + 1);
      }
    }

    // 计算每个员工的综合分数并创建/更新结果
    for (const employeeId of targetEmployeeIds) {
      const pendingKpi = kpiPendingCountByEmployee.get(employeeId) || 0;
      const pendingE360 = e360PendingCountByEmployee.get(employeeId) || 0;

      if (options.weights.kpi > 0 && pendingKpi > 0) {
        failedEmployees.push({ employeeId, reason: 'KPI 评估未完成' });
        continue;
      }
      if (options.weights.e360 > 0 && pendingE360 > 0) {
        failedEmployees.push({ employeeId, reason: '360 评估未完成' });
        continue;
      }

      // 获取各项分数
      const kpiData = employeeKpiScores.get(employeeId);
      const kpiScore = kpiData && kpiData.totalWeight > 0
        ? kpiData.totalScore / kpiData.totalWeight
        : null;
      const e360Score = employee360Scores.get(employeeId) ?? null;

      const kpiWeight = options.weights.kpi;
      const e360Weight = options.weights.e360;

      // 计算总分
      const totalScore = this.calculateTotalScore(
        kpiScore, kpiWeight,
        e360Score, e360Weight,
      );

      // 根据分数获取等级
      const grade = this.gradeConfigService.getGradeByScore(grades, totalScore);

      // 创建或更新结果
      const result = await this.prisma.performanceResult.upsert({
        where: {
          cycleId_employeeId: { cycleId, employeeId },
        },
        create: {
          cycleId,
          employeeId,
          organizationId: cycle.organizationId,
          kpiScore,
          kpiWeight,
          e360Score,
          e360Weight,
          totalScore,
          proposedGradeCode: grade?.code || 'B',
          proposedGradeName: grade?.name || '良好',
          gradeCode: grade?.code || 'B',
          gradeName: grade?.name || '良好',
          isPublished: false,
        },
        update: {
          kpiScore,
          kpiWeight,
          e360Score,
          e360Weight,
          totalScore,
          proposedGradeCode: grade?.code || 'B',
          proposedGradeName: grade?.name || '良好',
          gradeCode: grade?.code || 'B',
          gradeName: grade?.name || '良好',
          deletedAt: null,
        },
      });
      calculated++;
    }

    return {
      cycleId,
      calculated,
      failed: failedEmployees.length,
      failedEmployees,
    };
  }

  async recalculateGrades(cycleId: string) {
    const cycle = await this.prisma.performanceCycle.findFirst({
      where: { id: cycleId, deletedAt: null },
      include: { gradeConfig: true },
    });

    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,
      );
    }

    const grades = (cycle.gradeConfig?.grades as unknown as GradeDefinition[]) ||
      GradeConfigService.DEFAULT_GRADES;

    const results = await this.prisma.performanceResult.findMany({
      where: { cycleId, deletedAt: null, totalScore: { not: null } },
      select: { id: true, totalScore: true, gradeCode: true, gradeName: true },
    });

    // 按目标等级分组，使用 updateMany 批量更新
    const updatesByGrade = new Map<string, string[]>();
    for (const result of results) {
      const grade = this.gradeConfigService.getGradeByScore(grades, Number(result.totalScore));
      if (grade && (grade.code !== result.gradeCode || grade.name !== result.gradeName)) {
        const key = `${grade.code}::${grade.name}`;
        if (!updatesByGrade.has(key)) updatesByGrade.set(key, []);
        updatesByGrade.get(key)!.push(result.id);
      }
    }

    let updated = 0;
    for (const [key, ids] of updatesByGrade) {
      const [code, name] = key.split('::');
      const { count } = await this.prisma.performanceResult.updateMany({
        where: { id: { in: ids } },
        data: { proposedGradeCode: code, proposedGradeName: name, gradeCode: code, gradeName: name },
      });
      updated += count;
    }

    return { cycleId, updated };
  }

  // ==================== 结果发布 ====================

  async publishResults(
    cycleId: string,
    publishedBy: string,
    options?: { employeeIds?: string[]; notifyEmployees?: boolean },
  ) {
    const cycle = await this.prisma.performanceCycle.findFirst({
      where: { id: cycleId, deletedAt: null },
    });

    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,
      );
    }

    if (!['CONFIRMING', 'COMPLETED'].includes(cycle.status)) {
      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();

    const where: Prisma.PerformanceResultWhereInput = { cycleId, deletedAt: null };
    if (options?.employeeIds && options.employeeIds.length > 0) {
      where.employeeId = { in: options.employeeIds };
    }

    const existing = await this.prisma.performanceResult.findMany({
      where,
      select: { id: true, employeeId: true, isPublished: true },
    });

    const alreadyPublished = existing.filter((r) => r.isPublished).length;
    const publishTargets = existing.filter((r) => !r.isPublished);

    if (publishTargets.length > 0) {
      await this.prisma.performanceResult.updateMany({
        where: { id: { in: publishTargets.map((r) => r.id) } },
        data: {
          isPublished: true,
          publishedAt: now,
        },
      });
    }

    // 更新周期发布信息
    await this.prisma.performanceCycle.update({
      where: { id: cycleId },
      data: {
        resultsPublishedAt: now,
        resultsPublishedBy: publishedBy,
      },
    });

    let notified = 0;
    if (options?.notifyEmployees && publishTargets.length > 0) {
      const notifications = publishTargets.map((target) => ({
        channel: NotificationChannel.EMAIL,
        to: target.employeeId,
        subject: '绩效结果已发布',
        content: '您的绩效结果已发布，请前往绩效模块查看。',
        priority: 'NORMAL' as const,
        metadata: { cycleId },
      }));
      await this.notificationService.sendBatch(notifications);
      notified = notifications.length;
    }

    return {
      cycleId,
      published: publishTargets.length,
      alreadyPublished,
      notified,
    };
  }

  // ==================== 查询 ====================

  async findMyResults(
    userId: string,
    query?: { page?: number; pageSize?: number },
  ) {
    const page = query?.page || 1;
    const pageSize = query?.pageSize || 20;
    const skip = (page - 1) * pageSize;

    const [items, total] = await Promise.all([
      this.prisma.performanceResult.findMany({
        where: { employeeId: userId, isPublished: true, deletedAt: null },
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
        include: {
          cycle: {
            select: { id: true, name: true, type: true, gradeConfigId: true, gradeConfig: true },
          },
        },
      }),
      this.prisma.performanceResult.count({
        where: { employeeId: userId, isPublished: true, deletedAt: null },
      }),
    ]);

    const results = items.map((result) => {
      const grades = (result.cycle.gradeConfig?.grades as unknown as GradeDefinition[]) ||
        GradeConfigService.DEFAULT_GRADES;
      const grade = grades.find((g) => g.code === result.gradeCode);

      return {
        id: result.id,
        cycleId: result.cycleId,
        cycleName: result.cycle.name,
        cycleType: result.cycle.type,
        totalScore: Number(result.totalScore),
        proposedGradeCode: result.proposedGradeCode,
        proposedGradeName: result.proposedGradeName,
        gradeCode: result.gradeCode,
        gradeName: result.gradeName,
        gradeColor: grade?.color || '#1890ff',
        isPublished: result.isPublished,
        publishedAt: result.publishedAt,
        viewedByEmployee: result.viewedByEmployee,
        viewedAt: result.viewedAt,
        confirmStatus: result.confirmStatus,
        appealReason: result.appealReason,
      };
    });

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

  async findMyResultByCycle(userId: string, cycleId: string) {
    const result = await this.prisma.performanceResult.findFirst({
      where: { employeeId: userId, cycleId, deletedAt: null },
      include: {
        cycle: {
          include: {
            gradeConfig: true,
          },
        },
      },
    });

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

    if (!result.isPublished) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.message,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.code,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.httpStatus,
      );
    }

    const kpiAssignments = await this.prisma.kpiAssignment.findMany({
      where: { cycleId, employeeId: userId, deletedAt: null },
      include: {
        assessment: { select: { finalScore: true, managerScore: true } },
      },
    });

    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { displayName: true },
    });

    const grades = (result.cycle.gradeConfig?.grades as unknown as GradeDefinition[]) ||
      GradeConfigService.DEFAULT_GRADES;
    const grade = grades.find((g) => g.code === result.gradeCode);

    return {
      id: result.id,
      cycleId: result.cycleId,
      cycleName: result.cycle.name,
      employeeId: result.employeeId,
      employeeName: user?.displayName || '',
      kpiScore: result.kpiScore ? Number(result.kpiScore) : null,
      kpiWeight: result.kpiWeight ? Number(result.kpiWeight) : null,
      e360Score: result.e360Score ? Number(result.e360Score) : null,
      e360Weight: result.e360Weight ? Number(result.e360Weight) : null,
      totalScore: Number(result.totalScore),
      proposedGradeCode: result.proposedGradeCode,
      proposedGradeName: result.proposedGradeName,
      gradeCode: result.gradeCode,
      gradeName: result.gradeName,
      gradeColor: grade?.color || '#1890ff',
      scoreBreakdown: {
        kpiDetails: kpiAssignments.map((assignment) => ({
          indicatorName: assignment.name || '未命名指标',
          score: Number(assignment.assessment?.finalScore) ||
            Number(assignment.assessment?.managerScore) ||
            0,
          weight: Number(assignment.weight) || 0,
        })),
      },
      isPublished: result.isPublished,
      publishedAt: result.publishedAt,
      viewedByEmployee: result.viewedByEmployee,
      viewedAt: result.viewedAt,
      remarks: result.remarks,
      createdAt: result.createdAt,
      updatedAt: result.updatedAt,
    };
  }

  async findByCycle(
    cycleId: string,
    query?: {
      departmentId?: string;
      gradeCode?: string;
      isPublished?: boolean;
      page?: number;
      pageSize?: number;
    },
  ) {
    const { departmentId, gradeCode, isPublished, page = 1, pageSize = 20 } = query || {};
    const skip = (page - 1) * pageSize;

    const where: Prisma.PerformanceResultWhereInput = { cycleId, deletedAt: null };
    if (gradeCode) where.gradeCode = gradeCode;
    if (isPublished !== undefined) where.isPublished = isPublished;

    // 按部门筛选员工
    if (departmentId) {
      const departmentMemberships = await this.prisma.userDepartment.findMany({
        where: { departmentId, leftAt: null },
        select: { userId: true },
      });
      where.employeeId = { in: departmentMemberships.map((m) => m.userId) };
    }

    const [items, total, publishedCount, viewedCount, confirmedCount, appealedCount] = await Promise.all([
      this.prisma.performanceResult.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { totalScore: 'desc' },
      }),
      this.prisma.performanceResult.count({ where }),
      this.prisma.performanceResult.count({ where: { ...where, isPublished: true } }),
      this.prisma.performanceResult.count({ where: { ...where, viewedByEmployee: true } }),
      this.prisma.performanceResult.count({ where: { ...where, confirmStatus: 'CONFIRMED' } }),
      this.prisma.performanceResult.count({ where: { ...where, confirmStatus: 'APPEALED' } }),
    ]);

    const employeeIds = items.map((item) => item.employeeId);
    const [users, memberships] = await Promise.all([
      this.prisma.user.findMany({
        where: { id: { in: employeeIds } },
        select: { id: true, displayName: true },
      }),
      this.prisma.userDepartment.findMany({
        where: { userId: { in: employeeIds }, isPrimary: true, leftAt: null },
        include: { department: { select: { name: true } } },
      }),
    ]);

    const userMap = new Map(users.map((u) => [u.id, u.displayName]));
    const deptMap = new Map(memberships.map((m) => [m.userId, m.department?.name || '']));

    const resultItems = items.map((item) => ({
      id: item.id,
      employeeId: item.employeeId,
      employeeName: userMap.get(item.employeeId) || '',
      departmentName: deptMap.get(item.employeeId) || '',
      totalScore: Number(item.totalScore),
      proposedGradeCode: item.proposedGradeCode,
      proposedGradeName: item.proposedGradeName,
      gradeCode: item.gradeCode,
      gradeName: item.gradeName,
      isPublished: item.isPublished,
      viewedByEmployee: item.viewedByEmployee,
      confirmStatus: item.confirmStatus,
      appealReason: item.appealReason,
    }));

    return {
      items: resultItems,
      pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) },
      summary: {
        totalEmployees: total,
        publishedCount,
        viewedCount,
        confirmedCount,
        appealedCount,
      },
    };
  }

  async findById(id: string) {
    const result = await this.prisma.performanceResult.findFirst({
      where: { id, deletedAt: null },
    });

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

    return {
      id: result.id,
      cycleId: result.cycleId,
      employeeId: result.employeeId,
      kpiScore: result.kpiScore ? Number(result.kpiScore) : null,
      kpiWeight: result.kpiWeight ? Number(result.kpiWeight) : null,
      e360Score: result.e360Score ? Number(result.e360Score) : null,
      e360Weight: result.e360Weight ? Number(result.e360Weight) : null,
      totalScore: Number(result.totalScore),
      proposedGradeCode: result.proposedGradeCode,
      proposedGradeName: result.proposedGradeName,
      gradeCode: result.gradeCode,
      gradeName: result.gradeName,
      isPublished: result.isPublished,
      publishedAt: result.publishedAt,
      viewedByEmployee: result.viewedByEmployee,
      viewedAt: result.viewedAt,
      remarks: result.remarks,
      createdAt: result.createdAt,
      updatedAt: result.updatedAt,
      deletedAt: result.deletedAt,
    };
  }

  async getStatistics(cycleId: string) {
    const results = await this.prisma.performanceResult.findMany({
      where: { cycleId, deletedAt: null, totalScore: { not: null } },
      select: { totalScore: true, gradeCode: true, gradeName: true, isPublished: true, viewedByEmployee: true },
    });

    if (results.length === 0) {
      return {
        cycleId,
        totalEmployees: 0,
        averageScore: 0,
        gradeDistribution: [],
        publishedCount: 0,
        viewedCount: 0,
      };
    }

    // 计算平均分
    const totalScore = results.reduce((sum, r) => sum + Number(r.totalScore), 0);
    const averageScore = totalScore / results.length;

    // 统计等级分布
    const gradeMap = new Map<string, { code: string; name: string; count: number }>();
    for (const result of results) {
      const gc = result.gradeCode;
      if (!gc) continue;
      if (!gradeMap.has(gc)) {
        gradeMap.set(gc, {
          code: gc,
          name: result.gradeName || '',
          count: 0,
        });
      }
      gradeMap.get(gc)!.count++;
    }

    const gradeDistribution = Array.from(gradeMap.values()).map((g) => ({
      ...g,
      percentage: (g.count / results.length) * 100,
    }));

    return {
      cycleId,
      totalEmployees: results.length,
      averageScore,
      gradeDistribution,
      publishedCount: results.filter((r) => r.isPublished).length,
      viewedCount: results.filter((r) => r.viewedByEmployee).length,
    };
  }

  // ==================== 操作 ====================

  async markAsViewed(id: string, currentUserId: string) {
    const result = await this.findById(id);

    if (result.employeeId !== currentUserId) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.message,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.code,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.httpStatus,
      );
    }

    if (!result.isPublished) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.message,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.code,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED.httpStatus,
      );
    }

    if (result.viewedByEmployee) {
      return result;
    }

    return this.prisma.performanceResult.update({
      where: { id },
      data: {
        viewedByEmployee: true,
        viewedAt: new Date(),
      },
    });
  }

  async assignGrade(resultId: string, gradeCode: string, gradeName: string) {
    const result = await this.findById(resultId);

    if (result.isPublished) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.message,
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.code,
        PERFORMANCE_ERROR_CODES.COMMON_VALIDATION_FAILED.httpStatus,
        { reason: 'result_already_published' },
      );
    }

    return this.prisma.performanceResult.update({
      where: { id: resultId },
      data: {
        gradeCode,
        gradeName,
      },
    });
  }

  async addRemarks(resultId: string, remarks: string) {
    await this.findById(resultId);

    return this.prisma.performanceResult.update({
      where: { id: resultId },
      data: { remarks },
    });
  }

  async exportResults(payload: {
    cycleId: string;
    format: 'xlsx' | 'csv';
    fields?: string[];
  }): Promise<{ downloadUrl: string; expiresAt: string }> {
    const results = await this.prisma.performanceResult.findMany({
      where: { cycleId: payload.cycleId, deletedAt: null, totalScore: { not: null } },
      orderBy: { totalScore: 'desc' },
    });

    const employeeIds = results.map((r) => r.employeeId);
    const [users, memberships] = await Promise.all([
      this.prisma.user.findMany({
        where: { id: { in: employeeIds } },
        select: { id: true, displayName: true },
      }),
      this.prisma.userDepartment.findMany({
        where: { userId: { in: employeeIds }, isPrimary: true, leftAt: null },
        include: { department: { select: { name: true } } },
      }),
    ]);

    const userMap = new Map(users.map((u) => [u.id, u.displayName]));
    const deptMap = new Map(memberships.map((m) => [m.userId, m.department?.name || '']));

    const defaultFields = [
      'employeeId',
      'employeeName',
      'departmentName',
      'totalScore',
      'gradeCode',
      'gradeName',
    ];
    const fields = payload.fields && payload.fields.length > 0 ? payload.fields : defaultFields;

    const rows = results.map((r) => {
      const record: Record<string, string | number | boolean | null> = {
        employeeId: r.employeeId,
        employeeName: userMap.get(r.employeeId) || '',
        departmentName: deptMap.get(r.employeeId) || '',
        kpiScore: r.kpiScore ? Number(r.kpiScore) : null,
        kpiWeight: r.kpiWeight ? Number(r.kpiWeight) : null,
        e360Score: r.e360Score ? Number(r.e360Score) : null,
        e360Weight: r.e360Weight ? Number(r.e360Weight) : null,
        totalScore: Number(r.totalScore),
        gradeCode: r.gradeCode,
        gradeName: r.gradeName,
        isPublished: r.isPublished,
        remarks: r.remarks,
      };
      return fields.reduce<Record<string, string | number | boolean | null>>((acc, key) => {
        acc[key] = record[key] ?? null;
        return acc;
      }, {});
    });

    let buffer: Buffer;
    if (payload.format === 'xlsx') {
      const worksheet = XLSX.utils.json_to_sheet(rows);
      const workbook = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(workbook, worksheet, 'results');
      buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) as Buffer;
    } else {
      const header = fields.join(',');
      const lines = rows.map((row) =>
        fields
          .map((field) => {
            const value = row[field];
            if (value === null || value === undefined) return '';
            const escaped = String(value).replace(/"/g, '""');
            return `"${escaped}"`;
          })
          .join(','),
      );
      buffer = Buffer.from([header, ...lines].join('\n'), 'utf-8');
    }

    const base64 = buffer.toString('base64');
    const mimeType = payload.format === 'xlsx'
      ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      : 'text/csv';
    const downloadUrl = `data:${mimeType};base64,${base64}`;
    const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();

    return { downloadUrl, expiresAt };
  }

  // ==================== 员工确认/申诉 ====================

  /**
   * 员工确认结果
   */
  async confirmResult(resultId: string, userId: string) {
    const result = await this.prisma.performanceResult.findFirst({
      where: { id: resultId, deletedAt: null },
      include: { cycle: true },
    });

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

    if (result.employeeId !== userId) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.message,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.code,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.httpStatus,
      );
    }

    // 状态机守卫：周期必须处于 CONFIRMING
    if (result.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,
      );
    }

    // 状态机守卫：结果必须已发布
    if (!result.isPublished) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.message,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.code,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.httpStatus,
      );
    }

    // 状态机守卫：不可重复确认
    if (result.confirmStatus === 'CONFIRMED') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.message,
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.code,
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.httpStatus,
      );
    }

    return this.prisma.performanceResult.update({
      where: { id: resultId },
      data: { confirmStatus: 'CONFIRMED' },
    });
  }

  /**
   * 员工申诉
   */
  async appealResult(resultId: string, userId: string, reason: string) {
    const result = await this.prisma.performanceResult.findFirst({
      where: { id: resultId, deletedAt: null },
      include: { cycle: true },
    });

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

    if (result.employeeId !== userId) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.message,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.code,
        PERFORMANCE_ERROR_CODES.COMMON_FORBIDDEN.httpStatus,
      );
    }

    // 状态机守卫：周期必须处于 CONFIRMING
    if (result.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,
      );
    }

    // 状态机守卫：结果必须已发布
    if (!result.isPublished) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.message,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.code,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_PUBLISHED_FOR_CONFIRM.httpStatus,
      );
    }

    // 状态机守卫：申诉原因最少 10 字符
    if (!reason || reason.trim().length < 10) {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_APPEAL_REASON_TOO_SHORT.message,
        PERFORMANCE_ERROR_CODES.RESULT_APPEAL_REASON_TOO_SHORT.code,
        PERFORMANCE_ERROR_CODES.RESULT_APPEAL_REASON_TOO_SHORT.httpStatus,
      );
    }

    // 状态机守卫：已确认或已处理的结果不可再申诉
    if (result.confirmStatus === 'CONFIRMED' || result.confirmStatus === 'APPEAL_RESOLVED') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.message,
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.code,
        PERFORMANCE_ERROR_CODES.RESULT_ALREADY_CONFIRMED.httpStatus,
      );
    }

    return this.prisma.performanceResult.update({
      where: { id: resultId },
      data: {
        confirmStatus: 'APPEALED',
        appealReason: reason,
      },
    });
  }

  /**
   * HR 处理申诉
   */
  async resolveAppeal(
    resultId: string,
    data: { response: string; action: 'MAINTAIN' | 'ADJUST'; adjustedGrade?: string; adjustedScore?: number },
  ) {
    const result = await this.prisma.performanceResult.findFirst({
      where: { id: resultId, deletedAt: null },
    });

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

    // 幂等性：已处理的申诉直接返回
    if (result.confirmStatus === 'APPEAL_RESOLVED') {
      return result;
    }

    if (result.confirmStatus !== 'APPEALED') {
      throw new BusinessException(
        PERFORMANCE_ERROR_CODES.RESULT_NOT_APPEALED.message,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_APPEALED.code,
        PERFORMANCE_ERROR_CODES.RESULT_NOT_APPEALED.httpStatus,
      );
    }

    const updateData: any = {
      confirmStatus: 'APPEAL_RESOLVED',
      appealResponse: data.response,
      appealResolvedAt: new Date(),
    };

    // 如果 HR 选择调整等级/分数
    if (data.action === 'ADJUST') {
      if (data.adjustedGrade) {
        updateData.gradeCode = data.adjustedGrade;
        // 查询等级配置获取对应的 gradeName
        const cycle = await this.prisma.performanceCycle.findUnique({
          where: { id: result.cycleId },
          include: { gradeConfig: true },
        });
        const grades = ((cycle as any)?.gradeConfig?.grades as any[]) || [];
        const grade = grades.find((g: any) => g.code === data.adjustedGrade);
        if (grade?.name) {
          updateData.gradeName = grade.name;
        }
      }
      if (data.adjustedScore !== undefined) {
        updateData.totalScore = data.adjustedScore;
        updateData.kpiScore = data.adjustedScore;
      }
    }

    return this.prisma.performanceResult.update({
      where: { id: resultId },
      data: updateData,
    });
  }

  // ==================== 整体评语 ====================

  async saveOverallComment(dto: SaveOverallCommentDto, currentUserId: string) {
    // 校验：至少提供一个评语
    if (!dto.selfOverallComment && !dto.managerOverallComment) {
      throw new BadRequestException('至少提供 selfOverallComment 或 managerOverallComment');
    }

    // 权限校验：selfOverallComment 仅本人可写
    if (dto.selfOverallComment && currentUserId !== dto.employeeId) {
      throw new ForbiddenException('只能为自己写整体自评');
    }

    // managerOverallComment 仅目标员工的经理可写
    if (dto.managerOverallComment !== undefined) {
      const isManager = await this.prisma.userDepartment.findFirst({
        where: {
          userId: dto.employeeId,
          managerId: currentUserId,
          leftAt: null,
        },
      });
      if (!isManager) {
        throw new ForbiddenException('只有目标员工的经理可以写整体评语');
      }
    }

    // 获取周期的组织 ID
    const cycle = await this.prisma.performanceCycle.findUnique({
      where: { id: dto.cycleId },
      select: { organizationId: true },
    });
    if (!cycle) {
      throw new NotFoundException('周期不存在');
    }

    // upsert PerformanceResult
    const updateData: any = {};
    if (dto.selfOverallComment !== undefined) updateData.selfOverallComment = dto.selfOverallComment;
    if (dto.managerOverallComment !== undefined) updateData.managerOverallComment = dto.managerOverallComment;

    const result = await this.prisma.performanceResult.upsert({
      where: {
        cycleId_employeeId: { cycleId: dto.cycleId, employeeId: dto.employeeId },
      },
      create: {
        cycleId: dto.cycleId,
        employeeId: dto.employeeId,
        organizationId: cycle.organizationId,
        ...updateData,
      },
      update: updateData,
    });

    return result;
  }

  async getOverallComment(cycleId: string, employeeId: string, currentUserId: string) {
    // 读取隔离：本人、直属经理、或上级主管可查看
    if (currentUserId !== employeeId) {
      // 检查是否为直属经理
      const isDirectManager = await this.prisma.userDepartment.findFirst({
        where: {
          userId: employeeId,
          managerId: currentUserId,
          leftAt: null,
        },
      });
      if (!isDirectManager) {
        // 检查是否为上级主管（当前用户管理的部门包含该员工）
        const managedDepts = await this.prisma.userDepartment.findMany({
          where: { managerId: currentUserId, leftAt: null },
          select: { departmentId: true, organizationId: true },
        });
        const empDept = await this.prisma.userDepartment.findFirst({
          where: {
            userId: employeeId,
            leftAt: null,
            departmentId: { in: managedDepts.map((d) => d.departmentId) },
          },
        });
        if (!empDept) {
          throw new ForbiddenException('只有本人或其经理可以查看整体评语');
        }
      }
    }

    const result = await this.prisma.performanceResult.findUnique({
      where: {
        cycleId_employeeId: { cycleId, employeeId },
      },
      select: {
        selfOverallComment: true,
        managerOverallComment: true,
      },
    });

    return {
      selfOverallComment: result?.selfOverallComment || null,
      managerOverallComment: result?.managerOverallComment || null,
    };
  }

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

  private calculateTotalScore(
    kpiScore: number | null,
    kpiWeight: number | null,
    e360Score: number | null,
    e360Weight: number | null,
  ): number {
    const kpi = kpiScore !== null && kpiWeight !== null ? kpiScore * kpiWeight : 0;
    const e360 = e360Score !== null && e360Weight !== null ? e360Score * e360Weight : 0;
    return (kpi + e360) / 100;
  }
}
