"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 { autoRaiseRequisition, notifyAutoRaised } from "@/lib/requisition-service";
import { SeafarerDocType, PpeItem } from "@prisma/client";
import { z } from "zod";
import { revalidatePath } from "next/cache";

// Whole months between two dates (floored), min 0 — for the experience record.
function monthsBetween(from: Date, to: Date): number {
  const months = (to.getFullYear() - from.getFullYear()) * 12 + (to.getMonth() - from.getMonth()) - (to.getDate() < from.getDate() ? 1 : 0);
  return Math.max(0, months);
}

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

const crewPath = (id: string) => `/crewing/crew/${id}`;

async function guard(permission: Permission): Promise<{ error: string } | { userId: string }> {
  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 };
}

async function requireCrew(id: string) {
  return db.crewMember.findUnique({ where: { id }, select: { id: true } });
}

// ── Documents ──────────────────────────────────────────────────────────────

const docSchema = z.object({
  crewMemberId: z.string().min(1),
  docType: z.nativeEnum(SeafarerDocType),
  number: z.string().optional(),
  issueDate: z.string().optional(),
  expiryDate: z.string().optional(),
});

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

  const parsed = docSchema.safeParse({
    crewMemberId: formData.get("crewMemberId"),
    docType: formData.get("docType"),
    number: (formData.get("number") as string) || undefined,
    issueDate: (formData.get("issueDate") as string) || undefined,
    expiryDate: (formData.get("expiryDate") as string) || undefined,
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;
  if (!(await requireCrew(d.crewMemberId))) return { error: "Crew member not found" };

  let fileKey: string | null = null;
  const file = formData.get("file");
  if (file instanceof File && file.size > 0) {
    fileKey = buildStorageKey("crew-document", d.crewMemberId, file.name);
    await uploadBuffer(fileKey, Buffer.from(await file.arrayBuffer()), file.type || "application/octet-stream");
  }

  await db.seafarerDocument.create({
    data: {
      crewMemberId: d.crewMemberId,
      docType: d.docType,
      number: d.number ?? null,
      fileKey,
      issueDate: d.issueDate ? new Date(d.issueDate) : null,
      expiryDate: d.expiryDate ? new Date(d.expiryDate) : null,
    },
  });
  await db.crewAction.create({ data: { actionType: "DOCUMENT_UPLOADED", actorId: g.userId, crewMemberId: d.crewMemberId, metadata: { docType: d.docType } } });

  revalidatePath(crewPath(d.crewMemberId));
  return { ok: true };
}

export async function deleteDocument(id: string): Promise<ActionResult> {
  const g = await guard("upload_crew_records");
  if ("error" in g) return g;
  const doc = await db.seafarerDocument.findUnique({ where: { id }, select: { crewMemberId: true, docType: true } });
  if (!doc) return { error: "Document not found" };
  await db.$transaction(async (tx) => {
    await tx.seafarerDocument.delete({ where: { id } });
    await tx.crewAction.create({
      data: { actionType: "RECORD_DELETED", actorId: g.userId, crewMemberId: doc.crewMemberId, metadata: { record: "document", docType: doc.docType } },
    });
  });
  revalidatePath(crewPath(doc.crewMemberId));
  return { ok: true };
}

// ── Bank & EPF ───────────────────────────────────────────────────────────────

const bankEpfSchema = z.object({
  crewMemberId: z.string().min(1),
  accountName: z.string().optional(),
  accountNumber: z.string().optional(),
  ifsc: z.string().optional(),
  bankName: z.string().optional(),
  uan: z.string().optional(),
  aadhaarLast4: z.string().optional(),
  pfNumber: z.string().optional(),
});

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

  const parsed = bankEpfSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;
  if (!(await requireCrew(d.crewMemberId))) return { error: "Crew member not found" };

  await db.$transaction(async (tx) => {
    await tx.bankDetail.upsert({
      where: { crewMemberId: d.crewMemberId },
      update: { accountName: d.accountName, accountNumber: d.accountNumber, ifsc: d.ifsc, bankName: d.bankName },
      create: { crewMemberId: d.crewMemberId, accountName: d.accountName, accountNumber: d.accountNumber, ifsc: d.ifsc, bankName: d.bankName },
    });
    await tx.epfDetail.upsert({
      where: { crewMemberId: d.crewMemberId },
      update: { uan: d.uan, aadhaarLast4: d.aadhaarLast4, pfNumber: d.pfNumber },
      create: { crewMemberId: d.crewMemberId, uan: d.uan, aadhaarLast4: d.aadhaarLast4, pfNumber: d.pfNumber },
    });
    await tx.crewAction.create({ data: { actionType: "RECORD_UPDATED", actorId: g.userId, crewMemberId: d.crewMemberId, metadata: { record: "bank_epf" } } });
  });

  revalidatePath(crewPath(d.crewMemberId));
  return { ok: true };
}

// ── Next of kin / emergency ────────────────────────────────────────────────

const nokSchema = z.object({
  crewMemberId: z.string().min(1),
  name: z.string().trim().min(1, "Name is required"),
  relationship: z.string().optional(),
  phone: z.string().optional(),
  address: z.string().optional(),
  isEmergency: z.boolean().optional(),
});

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

  const parsed = nokSchema.safeParse({
    crewMemberId: formData.get("crewMemberId"),
    name: formData.get("name"),
    relationship: (formData.get("relationship") as string) || undefined,
    phone: (formData.get("phone") as string) || undefined,
    address: (formData.get("address") as string) || undefined,
    isEmergency: formData.get("isEmergency") === "on" || formData.get("isEmergency") === "true",
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;
  if (!(await requireCrew(d.crewMemberId))) return { error: "Crew member not found" };

  await db.nextOfKin.create({
    data: {
      crewMemberId: d.crewMemberId,
      name: d.name,
      relationship: d.relationship ?? null,
      phone: d.phone ?? null,
      address: d.address ?? null,
      isEmergency: d.isEmergency ?? false,
    },
  });
  await db.crewAction.create({ data: { actionType: "RECORD_UPDATED", actorId: g.userId, crewMemberId: d.crewMemberId, metadata: { record: "next_of_kin" } } });

  revalidatePath(crewPath(d.crewMemberId));
  return { ok: true };
}

export async function deleteNextOfKin(id: string): Promise<ActionResult> {
  const g = await guard("upload_crew_records");
  if ("error" in g) return g;
  const nok = await db.nextOfKin.findUnique({ where: { id }, select: { crewMemberId: true } });
  if (!nok) return { error: "Record not found" };
  await db.$transaction(async (tx) => {
    await tx.nextOfKin.delete({ where: { id } });
    await tx.crewAction.create({
      data: { actionType: "RECORD_DELETED", actorId: g.userId, crewMemberId: nok.crewMemberId, metadata: { record: "next_of_kin" } },
    });
  });
  revalidatePath(crewPath(nok.crewMemberId));
  return { ok: true };
}

// ── PPE ──────────────────────────────────────────────────────────────────────

const ppeSchema = z.object({
  crewMemberId: z.string().min(1),
  item: z.nativeEnum(PpeItem),
  size: z.string().optional(),
  quantity: z.coerce.number().int().min(1).default(1),
  comment: z.string().optional(),
});

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

  const parsed = ppeSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;
  if (!(await requireCrew(d.crewMemberId))) return { error: "Crew member not found" };

  await db.ppeIssue.create({
    data: { crewMemberId: d.crewMemberId, item: d.item, size: d.size ?? null, quantity: d.quantity, comment: d.comment ?? null, issuedById: g.userId },
  });
  await db.crewAction.create({ data: { actionType: "PPE_ISSUED", actorId: g.userId, crewMemberId: d.crewMemberId, metadata: { item: d.item } } });

  revalidatePath(crewPath(d.crewMemberId));
  return { ok: true };
}

export async function returnPpe(id: string): Promise<ActionResult> {
  const g = await guard("issue_ppe");
  if ("error" in g) return g;
  const ppe = await db.ppeIssue.findUnique({ where: { id }, select: { crewMemberId: true, returnedDate: true } });
  if (!ppe) return { error: "PPE record not found" };
  if (ppe.returnedDate) return { error: "Already returned" };
  await db.ppeIssue.update({ where: { id }, data: { returnedDate: new Date() } });
  await db.crewAction.create({ data: { actionType: "PPE_RETURNED", actorId: g.userId, crewMemberId: ppe.crewMemberId } });
  revalidatePath(crewPath(ppe.crewMemberId));
  return { ok: true };
}

// ── Experience ─────────────────────────────────────────────────────────────

const expSchema = z.object({
  crewMemberId: z.string().min(1),
  vesselType: z.string().optional(),
  rankId: z.string().optional(),
  fromDate: z.string().optional(),
  toDate: z.string().optional(),
  durationMonths: z.coerce.number().int().min(0).optional(),
});

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

  const parsed = expSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;
  if (!(await requireCrew(d.crewMemberId))) return { error: "Crew member not found" };

  await db.experienceRecord.create({
    data: {
      crewMemberId: d.crewMemberId,
      vesselType: d.vesselType ?? null,
      rankId: d.rankId || null,
      fromDate: d.fromDate ? new Date(d.fromDate) : null,
      toDate: d.toDate ? new Date(d.toDate) : null,
      durationMonths: d.durationMonths ?? null,
      source: "declared",
    },
  });
  await db.crewAction.create({ data: { actionType: "EXPERIENCE_ADDED", actorId: g.userId, crewMemberId: d.crewMemberId } });

  revalidatePath(crewPath(d.crewMemberId));
  return { ok: true };
}

// ── Sign off (Phase 4c, Epic K) ────────────────────────────────────────────────
// Ends a tour of duty: assignment → SIGNED_OFF, append an internal EXPERIENCE_RECORD,
// flip the crew member back to EX_HAND (so they return to the Candidates pool), and
// auto-raise a SIGN_OFF backfill requisition (reuses the Phase-2 helper).

export async function signOffCrew(assignmentId: string, signOffDate: string, remarks?: string): Promise<ActionResult> {
  const g = await guard("sign_off_crew");
  if ("error" in g) return g;
  if (!signOffDate) return { error: "A sign-off date is required" };

  const assignment = await db.crewAssignment.findUnique({
    where: { id: assignmentId },
    include: { vessel: { select: { name: true } }, site: { select: { name: true } } },
  });
  if (!assignment) return { error: "Assignment not found" };
  if (assignment.status === "SIGNED_OFF") return { error: "This crew member has already signed off" };

  const off = new Date(signOffDate);

  // Sign-off + the backfill requisition commit atomically (spec §5.3/§11): the
  // seat can never become vacant without its backfill being raised.
  const backfill = await db.$transaction(async (tx) => {
    await tx.crewAssignment.update({ where: { id: assignmentId }, data: { status: "SIGNED_OFF", signOffDate: off } });
    await tx.experienceRecord.create({
      data: {
        crewMemberId: assignment.crewMemberId,
        rankId: assignment.rankId,
        vesselType: assignment.vessel?.name ?? assignment.site?.name ?? null,
        fromDate: assignment.signOnDate,
        toDate: off,
        durationMonths: monthsBetween(assignment.signOnDate, off),
        source: "internal",
      },
    });
    // Same entity: flip EMPLOYEE → EX_HAND; they reappear in Candidates as a
    // returning hand. The ex-hand flag lives on type/status — their original
    // source (how they were first recruited) is preserved. currentRank (rank
    // held) is refreshed to the tour they just signed off from.
    await tx.crewMember.update({
      where: { id: assignment.crewMemberId },
      data: { status: "EX_HAND", type: "EX_HAND", currentRankId: assignment.rankId },
    });
    await tx.crewAction.create({
      data: { actionType: "CREW_SIGNED_OFF", actorId: g.userId, crewMemberId: assignment.crewMemberId, note: remarks?.trim() || null },
    });
    return autoRaiseRequisition(
      { rankId: assignment.rankId, vesselId: assignment.vesselId, siteId: assignment.siteId, reason: "SIGN_OFF" },
      tx
    );
  });
  // Notify the office after the transaction commits.
  await notifyAutoRaised(backfill);

  revalidatePath(crewPath(assignment.crewMemberId));
  revalidatePath("/crewing/crew");
  return { ok: true };
}
