"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 {
  canCancel,
  canPerformAction,
  getTransition,
  type RequisitionAction,
} from "@/lib/requisition-state-machine";
import {
  createRequisitionTx,
  getMpoRecipients,
  getOfficeRecipients,
  requisitionLocationLabel,
} from "@/lib/requisition-service";
import { notifyCrew } from "@/lib/notifier";
import { RequisitionReason } 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/requisitions";

// Crewing flag + permission guard. Returns the actor on success.
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 };
}

// ── Raise a requisition (MPO / Manager) ───────────────────────────────────────

const raiseSchema = z
  .object({
    rankId: z.string().min(1, "Rank is required"),
    vesselId: z.string().optional(),
    siteId: z.string().optional(),
    reason: z.nativeEnum(RequisitionReason).default("NEW_VACANCY"),
    neededBy: z.string().optional(),
    notes: z.string().optional(),
  })
  .refine((d) => Boolean(d.vesselId) || Boolean(d.siteId), {
    message: "A vessel or site is required",
  });

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

  const parsed = raiseSchema.safeParse({
    rankId: formData.get("rankId"),
    vesselId: (formData.get("vesselId") as string) || undefined,
    siteId: (formData.get("siteId") as string) || undefined,
    reason: (formData.get("reason") as string) || undefined,
    neededBy: (formData.get("neededBy") as string) || undefined,
    notes: (formData.get("notes") as string) || undefined,
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

  const requisition = await db.$transaction((tx) =>
    createRequisitionTx(tx, {
      rankId: d.rankId,
      vesselId: d.vesselId || null,
      siteId: d.siteId || null,
      reason: d.reason,
      neededBy: d.neededBy ? new Date(d.neededBy) : null,
      notes: d.notes || null,
      raisedById: g.userId,
    })
  );

  // Notify the MPO pool so it can start sourcing (spec §11). Don't self-notify.
  const recipients = (await getMpoRecipients()).filter((u) => u.id !== g.userId);
  if (recipients.length) {
    const loc = requisitionLocationLabel(requisition);
    await notifyCrew({
      event: "REQUISITION_RAISED",
      recipients,
      subject: `Requisition ${requisition.code} raised`,
      body: `A ${requisition.rank.name} vacancy on ${loc} has been raised (${requisition.code}).`,
      link: `${LIST_PATH}/${requisition.id}`,
    });
  }

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

// ── Withdraw / cancel a requisition (Manager, from OPEN/SHORTLISTING) ──────────

export async function cancelRequisition(id: string, reason: string): Promise<ActionResult> {
  const g = await guard("cancel_requisition");
  if ("error" in g) return g;

  const trimmed = reason?.trim();
  if (!trimmed) return { error: "A reason is required to withdraw a requisition" };

  const req = await db.requisition.findUnique({ where: { id }, select: { status: true } });
  if (!req) return { error: "Requisition not found" };
  if (!canCancel(req.status, g.role)) {
    return { error: `A requisition cannot be withdrawn once it is ${req.status}` };
  }

  await db.requisition.update({
    where: { id },
    data: {
      status: "CANCELLED",
      cancelledAt: new Date(),
      cancellationReason: trimmed,
      actions: {
        create: { actionType: "REQUISITION_CANCELLED", actorId: g.userId, note: trimmed },
      },
    },
  });

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

// ── Advance a requisition through the pipeline stages ──────────────────────────
// Phase 2 exposes the transitions; the recruitment pipeline (Phase 3) drives
// them as candidates progress. Role gating comes from the state machine.

export async function transitionRequisition(
  id: string,
  action: RequisitionAction
): Promise<ActionResult> {
  if (!CREWING_ENABLED) return { error: "Crewing is not enabled" };
  const session = await auth();
  if (!session?.user) return { error: "Unauthorized" };

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

  const transition = getTransition(req.status, action);
  if (!transition) return { error: `Cannot ${action} from ${req.status}` };
  if (!canPerformAction(req.status, action, session.user.role)) return { error: "Unauthorized" };

  await db.requisition.update({
    where: { id },
    data: {
      status: transition.to,
      filledAt: transition.to === "FILLED" ? new Date() : undefined,
      actions: {
        create: {
          actionType: transition.to === "FILLED" ? "REQUISITION_FILLED" : "REQUISITION_ADVANCED",
          actorId: session.user.id,
          metadata: { from: req.status, to: transition.to },
        },
      },
    },
  });

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

// ── Relief cover request (site staff) ──────────────────────────────────────────
// Site staff flag a foreseen gap; the office converts it into a requisition. The
// site-staff origination UI lands with the Leave/clash screen (Phase 4); the
// action exists now so the office-side convert flow and auto-raise share a path.

const reliefSchema = z
  .object({
    rankId: z.string().min(1, "Rank is required"),
    vesselId: z.string().optional(),
    siteId: z.string().optional(),
    note: z.string().optional(),
  })
  .refine((d) => Boolean(d.vesselId) || Boolean(d.siteId), {
    message: "A vessel or site is required",
  });

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

  const parsed = reliefSchema.safeParse({
    rankId: formData.get("rankId"),
    vesselId: (formData.get("vesselId") as string) || undefined,
    siteId: (formData.get("siteId") as string) || undefined,
    note: (formData.get("note") as string) || undefined,
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

  const relief = await db.$transaction(async (tx) => {
    const created = await tx.reliefRequest.create({
      data: {
        rankId: d.rankId,
        vesselId: d.vesselId || null,
        siteId: d.siteId || null,
        note: d.note || null,
        requestedById: g.userId,
      },
      include: { rank: true, vessel: true, site: true },
    });
    // CrewAction has no relief relation; record the id in metadata.
    await tx.crewAction.create({
      data: {
        actionType: "RELIEF_REQUESTED",
        actorId: g.userId,
        metadata: { reliefRequestId: created.id, rankId: d.rankId },
      },
    });
    return created;
  });

  const recipients = await getOfficeRecipients();
  if (recipients.length) {
    const loc = requisitionLocationLabel(relief);
    await notifyCrew({
      event: "RELIEF_REQUESTED",
      recipients,
      subject: `Relief cover requested — ${relief.rank.name} on ${loc}`,
      body: `A site has requested relief cover for a ${relief.rank.name} on ${loc}. Convert it to a requisition to start sourcing.`,
      link: LIST_PATH,
    });
  }

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

// ── Convert a relief request into a requisition (MPO / Manager) ────────────────

const convertSchema = z.object({
  reliefRequestId: z.string().min(1, "Relief request is required"),
  reason: z.nativeEnum(RequisitionReason).default("REPLACEMENT"),
  neededBy: z.string().optional(),
  notes: z.string().optional(),
});

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

  const parsed = convertSchema.safeParse({
    reliefRequestId: formData.get("reliefRequestId"),
    reason: (formData.get("reason") as string) || undefined,
    neededBy: (formData.get("neededBy") as string) || undefined,
    notes: (formData.get("notes") as string) || undefined,
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

  const relief = await db.reliefRequest.findUnique({ where: { id: d.reliefRequestId } });
  if (!relief) return { error: "Relief request not found" };
  if (relief.status !== "OPEN") return { error: "This relief request has already been handled" };

  const requisition = await db.$transaction(async (tx) => {
    const req = await createRequisitionTx(tx, {
      rankId: relief.rankId,
      vesselId: relief.vesselId,
      siteId: relief.siteId,
      reason: d.reason,
      neededBy: d.neededBy ? new Date(d.neededBy) : null,
      notes: d.notes || null,
      raisedById: g.userId,
    });
    await tx.reliefRequest.update({
      where: { id: relief.id },
      data: { status: "CONVERTED", convertedRequisitionId: req.id },
    });
    await tx.crewAction.create({
      data: {
        actionType: "RELIEF_CONVERTED",
        actorId: g.userId,
        requisitionId: req.id,
        metadata: { reliefRequestId: relief.id },
      },
    });
    return req;
  });

  // Let the requester know their relief request became a requisition.
  const requester = await db.user.findUnique({ where: { id: relief.requestedById } });
  if (requester && requester.isActive && requester.id !== g.userId) {
    const loc = requisitionLocationLabel(requisition);
    await notifyCrew({
      event: "RELIEF_CONVERTED",
      recipients: [requester],
      subject: `Relief cover converted — ${requisition.code}`,
      body: `Your relief request for a ${requisition.rank.name} on ${loc} is now requisition ${requisition.code}.`,
      link: `${LIST_PATH}/${requisition.id}`,
    });
  }

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