"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 { leaveCausesClash } from "@/lib/leave-clash";
import { autoRaiseRequisition, notifyAutoRaised, getManagerRecipients } from "@/lib/requisition-service";
import { notifyCrew } from "@/lib/notifier";
import { LeaveType } 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 LEAVE_PATH = "/crewing/leave";

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

function revalidate() {
  revalidatePath(LEAVE_PATH);
  revalidatePath("/approvals");
}

// ── Apply for leave (Site staff, on behalf of a crew member) ───────────────────

const applySchema = z
  .object({
    assignmentId: z.string().min(1, "Crew member is required"),
    type: z.nativeEnum(LeaveType).default("ANNUAL"),
    fromDate: z.string().min(1, "From date is required"),
    toDate: z.string().min(1, "To date is required"),
    reason: z.string().optional(),
  })
  .refine((d) => new Date(d.toDate) >= new Date(d.fromDate), { message: "To date must be on or after the from date" });

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

  const parsed = applySchema.safeParse({
    assignmentId: formData.get("assignmentId"),
    type: (formData.get("type") as string) || undefined,
    fromDate: formData.get("fromDate"),
    toDate: formData.get("toDate"),
    reason: (formData.get("reason") as string) || undefined,
  });
  if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" };
  const d = parsed.data;

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

  const leave = await db.leaveRequest.create({
    data: {
      assignmentId: d.assignmentId,
      type: d.type,
      fromDate: new Date(d.fromDate),
      toDate: new Date(d.toDate),
      reason: d.reason ?? null,
      appliedById: g.userId,
    },
  });
  await db.crewAction.create({ data: { actionType: "LEAVE_APPLIED", actorId: g.userId, crewMemberId: assignment.crewMember.id } });

  const managers = await getManagerRecipients();
  await notifyCrew({
    event: "LEAVE_FOR_APPROVAL",
    recipients: managers,
    subject: `Leave for approval — ${assignment.crewMember.name}`,
    body: `${assignment.crewMember.name} (${assignment.rank.name}) has a leave request from ${d.fromDate} to ${d.toDate} awaiting your decision.`,
    link: LEAVE_PATH,
  });

  revalidate();
  return { ok: true, id: leave.id };
}

// ── Decide leave (Manager) ─────────────────────────────────────────────────────
// On approval the assignment goes ON_LEAVE and a clash check runs; if it would
// leave the vessel with no same-rank cover, a LEAVE requisition is auto-raised.

export async function decideLeave(id: string, approve: boolean, note?: string): Promise<ActionResult> {
  const g = await guard("decide_leave");
  if ("error" in g) return g;

  const leave = await db.leaveRequest.findUnique({
    where: { id },
    include: { assignment: { select: { id: true, crewMemberId: true, rankId: true, vesselId: true, siteId: true } } },
  });
  if (!leave) return { error: "Leave request not found" };
  if (leave.status !== "APPLIED") return { error: `This leave request is already ${leave.status}` };
  if (!approve && !note?.trim()) return { error: "A reason is required to decline" };

  if (!approve) {
    await db.leaveRequest.update({ where: { id }, data: { status: "REJECTED", decidedById: g.userId, decidedAt: new Date(), reason: note?.trim() || leave.reason } });
    await db.crewAction.create({ data: { actionType: "LEAVE_DECIDED", actorId: g.userId, crewMemberId: leave.assignment.crewMemberId, note: note?.trim() || null, metadata: { decision: "REJECTED" } } });
    revalidate();
    return { ok: true };
  }

  // Leave approval + the clash check + any backfill requisition commit atomically
  // (spec §5.3/§11): an approved leave can never leave a cover gap un-raised.
  const backfill = await db.$transaction(async (tx) => {
    await tx.leaveRequest.update({ where: { id }, data: { status: "APPROVED", decidedById: g.userId, decidedAt: new Date() } });
    await tx.crewAssignment.update({ where: { id: leave.assignment.id }, data: { status: "ON_LEAVE" } });
    await tx.crewAction.create({ data: { actionType: "LEAVE_DECIDED", actorId: g.userId, crewMemberId: leave.assignment.crewMemberId, metadata: { decision: "APPROVED" } } });
    const clash = await leaveCausesClash(tx, {
      assignmentId: leave.assignment.id,
      rankId: leave.assignment.rankId,
      vesselId: leave.assignment.vesselId,
      fromDate: leave.fromDate,
      toDate: leave.toDate,
    });
    if (!clash) return null;
    return autoRaiseRequisition(
      { rankId: leave.assignment.rankId, vesselId: leave.assignment.vesselId, siteId: leave.assignment.siteId, reason: "LEAVE" },
      tx
    );
  });

  // Notify the office after the transaction commits.
  if (backfill) await notifyAutoRaised(backfill);

  revalidate();
  return { ok: true };
}
