"use server";

import { auth } from "@/auth";
import { db } from "@/lib/db";
import { hasPermission, type Permission } from "@/lib/permissions";
import { CREWING_ENABLED } from "@/lib/feature-flags";
import { buildStorageKey, uploadBuffer } from "@/lib/storage";
import { CandidateSource } from "@prisma/client";
import type { Role } from "@prisma/client";
import { z } from "zod";
import { revalidatePath } from "next/cache";

type ActionResult = { ok: true; id?: string } | { error: string };

const LIST_PATH = "/crewing/candidates";

async function guard(
  permission: Permission
): Promise<{ error: string } | { userId: string; role: Role }> {
  if (!CREWING_ENABLED) return { error: "Crewing is not enabled" };
  const session = await auth();
  if (!session?.user) return { error: "Unauthorized" };
  if (!hasPermission(session.user.role, permission)) return { error: "Unauthorized" };
  return { userId: session.user.id, role: session.user.role };
}

const candidateSchema = z.object({
  name: z.string().trim().min(1, "Name is required"),
  source: z.nativeEnum(CandidateSource).default("CAREERS"),
  appliedRankId: z.string().optional(),
  currentRankId: z.string().optional(),
  experienceMonths: z.coerce.number().int().min(0).max(720).default(0),
  vesselTypeExperience: z.string().optional(),
  email: z.string().trim().email("Enter a valid email").optional().or(z.literal("")),
  phone: z.string().optional(),
  notes: z.string().optional(),
});

function parse(formData: FormData) {
  return candidateSchema.safeParse({
    name: formData.get("name"),
    source: (formData.get("source") as string) || undefined,
    appliedRankId: (formData.get("appliedRankId") as string) || undefined,
    currentRankId: (formData.get("currentRankId") as string) || undefined,
    experienceMonths: (formData.get("experienceMonths") as string) || undefined,
    vesselTypeExperience: (formData.get("vesselTypeExperience") as string) || undefined,
    email: (formData.get("email") as string) || undefined,
    phone: (formData.get("phone") as string) || undefined,
    notes: (formData.get("notes") as string) || undefined,
  });
}

// Store an optional CV upload and return its storage key (null if none).
async function storeCv(formData: FormData, crewMemberId: string): Promise<string | null> {
  const file = formData.get("cv");
  if (!(file instanceof File) || file.size === 0) return null;
  const key = buildStorageKey("cv", crewMemberId, file.name);
  const buffer = Buffer.from(await file.arrayBuffer());
  await uploadBuffer(key, buffer, file.type || "application/octet-stream");
  return key;
}

export async function addCandidate(formData: FormData): Promise<ActionResult> {
  const g = await guard("manage_candidates");
  if ("error" in g) return g;

  const parsed = parse(formData);
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

  // B3 AC1 — ex-hand recognition: a returning person re-entered as a fresh
  // candidate is matched to their existing EX_HAND pool record by a stable key —
  // email when given, else an exact name match — and the SAME row is reused (so
  // their tour history, documents and bank stay on file) rather than creating a
  // duplicate. (Ex-hand is set by the office on the admin crew record; the
  // candidate form never tags it directly. Heuristic: with no DOB on file a
  // name-only match can in theory collide; email is preferred when available.)
  const match = await db.crewMember.findFirst({
    where: {
      status: "EX_HAND",
      ...(d.email
        ? { email: { equals: d.email, mode: "insensitive" } }
        : { name: { equals: d.name, mode: "insensitive" } }),
    },
    select: { id: true, appliedRankId: true, currentRankId: true, email: true, phone: true, notes: true, experienceMonths: true, vesselTypeExperience: true },
  });
  if (match) {
    const updated = await db.crewMember.update({
      where: { id: match.id },
      data: {
        // Keep EX_HAND type/status; refresh the application's details, never
        // discarding prior history (take the larger recorded experience).
        appliedRankId: d.appliedRankId || match.appliedRankId,
        currentRankId: d.currentRankId || match.currentRankId,
        email: d.email || match.email,
        phone: d.phone || match.phone,
        notes: d.notes || match.notes,
        experienceMonths: Math.max(d.experienceMonths, match.experienceMonths),
        vesselTypeExperience: d.vesselTypeExperience || match.vesselTypeExperience,
        actions: { create: { actionType: "CANDIDATE_UPDATED", actorId: g.userId, metadata: { exHandRecognized: true } } },
      },
    });
    const cvKey = await storeCv(formData, updated.id);
    if (cvKey) await db.crewMember.update({ where: { id: updated.id }, data: { cvKey } });
    revalidatePath(LIST_PATH);
    return { ok: true, id: updated.id };
  }

  const candidate = await db.crewMember.create({
    data: {
      name: d.name,
      source: d.source,
      // The candidate form always intakes a fresh NEW candidate. Ex-hand status
      // is an office/admin designation set on the crew record, not here.
      type: "NEW",
      status: "CANDIDATE",
      appliedRankId: d.appliedRankId || null,
      currentRankId: d.currentRankId || null,
      experienceMonths: d.experienceMonths,
      vesselTypeExperience: d.vesselTypeExperience || null,
      email: d.email || null,
      phone: d.phone || null,
      notes: d.notes || null,
      actions: { create: { actionType: "CANDIDATE_ADDED", actorId: g.userId } },
    },
  });

  const cvKey = await storeCv(formData, candidate.id);
  if (cvKey) await db.crewMember.update({ where: { id: candidate.id }, data: { cvKey } });

  revalidatePath(LIST_PATH);
  return { ok: true, id: candidate.id };
}

export async function updateCandidate(formData: FormData): Promise<ActionResult> {
  const g = await guard("manage_candidates");
  if ("error" in g) return g;

  const id = formData.get("id") as string;
  if (!id) return { error: "Candidate ID is required" };

  const parsed = parse(formData);
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

  const existing = await db.crewMember.findUnique({ where: { id }, select: { status: true } });
  if (!existing) return { error: "Candidate not found" };

  const cvKey = await storeCv(formData, id);

  await db.crewMember.update({
    where: { id },
    data: {
      name: d.name,
      source: d.source,
      // type/status are left untouched — ex-hand / employee designation is owned
      // by the office (admin crew record + sign-off), never by a candidate edit.
      appliedRankId: d.appliedRankId || null,
      currentRankId: d.currentRankId || null,
      experienceMonths: d.experienceMonths,
      vesselTypeExperience: d.vesselTypeExperience || null,
      email: d.email || null,
      phone: d.phone || null,
      notes: d.notes || null,
      ...(cvKey ? { cvKey } : {}),
      actions: { create: { actionType: "CANDIDATE_UPDATED", actorId: g.userId } },
    },
  });

  revalidatePath(LIST_PATH);
  revalidatePath(`${LIST_PATH}/${id}`);
  return { ok: true, id };
}
