import { prisma } from "@/lib/prisma";
import { requireUser, withGuard, HttpError } from "@/lib/guard";
import { parseBody, ok } from "@/lib/http";
import { appraisalUpdateSchema } from "@/lib/validators";
import { can, isManagerTier } from "@/lib/rbac";
import { progressPct } from "@/lib/utils";
import type { Prisma, AppraisalStatus } from "@prisma/client";

export const runtime = "nodejs";

type Ctx = { params: Promise<{ id: string }> };

const INCLUDE = {
  user: { select: { id: true, name: true, position: true, email: true } },
  reviewer: { select: { id: true, name: true } },
  competencies: { orderBy: { order: "asc" } },
} satisfies Prisma.AppraisalInclude;

// First & last (exclusive) calendar bounds + month numbers for a half-year.
function periodBounds(year: number, half: string) {
  const startMonth = half === "H1" ? 1 : 7;
  const start = new Date(year, startMonth - 1, 1);
  const end = half === "H1" ? new Date(year, 6, 1) : new Date(year + 1, 0, 1);
  const months = half === "H1" ? [1, 2, 3, 4, 5, 6] : [7, 8, 9, 10, 11, 12];
  return { start, end, months };
}

// GET /api/appraisals/[id] — detail + an auto performance snapshot for the half.
export const GET = withGuard(async (_req: Request, ctx: Ctx) => {
  const me = await requireUser();
  const { id } = await ctx.params;
  const appraisal = await prisma.appraisal.findUnique({ where: { id }, include: INCLUDE });
  if (!appraisal) throw new HttpError(404, "Appraisal not found");
  if (!isManagerTier(me.role) && appraisal.userId !== me.id)
    throw new HttpError(403, "You can only view your own appraisal");

  const { start, end, months } = periodBounds(appraisal.year, appraisal.half);

  // Personal KPI attainment over the period.
  const personalKpis = await prisma.kpiIndicator.findMany({
    where: {
      type: "PERSONAL",
      OR: [
        { assignedUserId: appraisal.userId },
        ...(appraisal.user.position
          ? [{ assignedUserId: null, assignedPosition: appraisal.user.position }]
          : []),
      ],
    },
    include: { monthlyValues: { where: { year: appraisal.year, month: { in: months } } } },
  });
  let sumPct = 0;
  let n = 0;
  for (const k of personalKpis) {
    for (const mv of k.monthlyValues) {
      sumPct += progressPct(mv.actualValue, mv.targetValue ?? k.targetValue);
      n++;
    }
  }

  const [tasks, postsPublished] = await Promise.all([
    prisma.task.findMany({ where: { userId: appraisal.userId, createdAt: { gte: start, lt: end } } }),
    prisma.post.count({
      where: { authorId: appraisal.userId, status: "POSTED", postedDate: { gte: start, lt: end } },
    }),
  ]);

  const scored = appraisal.competencies.filter((c) => c.score != null);
  const competencyAvg = scored.length
    ? Math.round((scored.reduce((s, c) => s + (c.score ?? 0), 0) / scored.length) * 10) / 10
    : null;

  return ok({
    ...appraisal,
    snapshot: {
      kpiAttainment: n ? Math.round(sumPct / n) : null,
      kpiCount: personalKpis.length,
      tasksTotal: tasks.length,
      tasksCompleted: tasks.filter((t) => t.status === "COMPLETED").length,
      postsPublished,
      competencyAvg,
    },
  });
});

const TRANSITIONS: Record<string, { from: AppraisalStatus[]; to: AppraisalStatus; who: "manager" | "subject" }> = {
  request_self: { from: ["DRAFT"], to: "SELF_REVIEW", who: "manager" },
  submit_self: { from: ["SELF_REVIEW"], to: "MANAGER_REVIEW", who: "subject" },
  finalize: { from: ["DRAFT", "SELF_REVIEW", "MANAGER_REVIEW"], to: "FINALIZED", who: "manager" },
  acknowledge: { from: ["FINALIZED"], to: "ACKNOWLEDGED", who: "subject" },
  reopen: { from: ["SELF_REVIEW", "MANAGER_REVIEW", "FINALIZED", "ACKNOWLEDGED"], to: "DRAFT", who: "manager" },
};

// PATCH /api/appraisals/[id] — edit fields, score competencies, drive workflow.
export const PATCH = withGuard(async (req: Request, ctx: Ctx) => {
  const me = await requireUser();
  const { id } = await ctx.params;
  const data = await parseBody(req, appraisalUpdateSchema);

  const appraisal = await prisma.appraisal.findUnique({ where: { id }, include: { competencies: true } });
  if (!appraisal) throw new HttpError(404, "Appraisal not found");

  const isManager = can(me.role, "appraisals:manage");
  const isSubject = appraisal.userId === me.id;
  if (!isManager && !isSubject) throw new HttpError(403, "Forbidden");

  const update: Prisma.AppraisalUpdateInput = {};

  // ── Workflow action ──
  if (data.action) {
    const t = TRANSITIONS[data.action];
    if (!t.from.includes(appraisal.status))
      throw new HttpError(400, `Cannot ${data.action} when status is ${appraisal.status}`);
    if (t.who === "manager" && !isManager) throw new HttpError(403, "Only a manager can do this");
    if (t.who === "subject" && !isSubject) throw new HttpError(403, "Only the staff member can do this");
    if (data.action === "finalize" && !(data.overallBand ?? appraisal.overallBand))
      throw new HttpError(400, "Set an overall rating before finalizing");
    update.status = t.to;
    if (data.action === "acknowledge") update.acknowledgedAt = new Date();
    if (data.action === "reopen") update.acknowledgedAt = null;
  }

  // ── Manager-only fields ──
  const managerFields = [
    "overallBand", "strengths", "improvements", "managerComments", "remarks", "goalsReview", "goalsNext",
  ] as const;
  const touchesManager = managerFields.some((f) => data[f] !== undefined) || data.competencies !== undefined;
  if (touchesManager) {
    if (!isManager) throw new HttpError(403, "Only a manager can edit the review");
    if (appraisal.status === "ACKNOWLEDGED") throw new HttpError(400, "Appraisal is signed off and locked");
    for (const f of managerFields) {
      if (data[f] !== undefined) (update as Record<string, unknown>)[f] = data[f] || null;
    }
  }

  // ── Staff self-assessment fields ──
  const touchesSelf = data.selfComments !== undefined || data.selfRating !== undefined;
  if (touchesSelf) {
    if (!isSubject && !isManager) throw new HttpError(403, "Forbidden");
    if (["FINALIZED", "ACKNOWLEDGED"].includes(appraisal.status) && !isManager)
      throw new HttpError(400, "Self-assessment is closed for this stage");
    if (data.selfComments !== undefined) update.selfComments = data.selfComments || null;
    if (data.selfRating !== undefined) update.selfRating = data.selfRating ?? null;
  }

  // ── Competency scores (manager) ──
  if (data.competencies) {
    const valid = new Set(appraisal.competencies.map((c) => c.id));
    for (const c of data.competencies) {
      if (!valid.has(c.id)) continue;
      await prisma.appraisalCompetency.update({
        where: { id: c.id },
        data: { score: c.score ?? null, note: c.note || null },
      });
    }
  }

  if (Object.keys(update).length > 0) {
    await prisma.appraisal.update({ where: { id }, data: update });
  }

  const fresh = await prisma.appraisal.findUnique({ where: { id }, include: INCLUDE });
  return ok(fresh);
});

// DELETE /api/appraisals/[id] — remove an appraisal (Manager-tier).
export const DELETE = withGuard(async (_req: Request, ctx: Ctx) => {
  const me = await requireUser();
  if (!can(me.role, "appraisals:manage")) throw new HttpError(403, "Forbidden");
  const { id } = await ctx.params;
  await prisma.appraisal.delete({ where: { id } }).catch(() => {
    throw new HttpError(404, "Appraisal not found");
  });
  return ok({ ok: true });
});
