/**
 * Integration tests for the self-contained crewing follow-ups:
 *  - SITE_STAFF login creation on placement/onboarding (grantsLogin ranks)
 *  - PPE / next-of-kin verification gates
 * (Own-site scoping is exercised via the siteId set on the created login.)
 */
import { vi, describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";

vi.mock("@/auth", () => ({ auth: vi.fn() }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
vi.mock("@/lib/feature-flags", () => ({ CREWING_ENABLED: true, INVENTORY_ENABLED: true }));
vi.mock("@/lib/notifier", () => ({ notify: vi.fn(), notifyCrew: vi.fn() }));

import { auth } from "@/auth";
import { db } from "@/lib/db";
import { placeCrew } from "@/app/(portal)/admin/crew/actions";
import { verifyPpe, verifyNextOfKin } from "@/app/(portal)/crewing/verification/actions";
import { makeSession, getSeedUser, fd } from "./helpers";
import type { Role } from "@prisma/client";

let managerId: string;
let manningId: string;
let accountsId: string;
let loginRankId: string;
let plainRankId: string;
let siteId: string;

const as = (userId: string, role: Role) =>
  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(userId, role));

const LOGIN_EMAIL = "pmlogin.itfu@example.local";

beforeAll(async () => {
  managerId = (await getSeedUser("manager@pelagia.local")).id;
  manningId = (await getSeedUser("manning@pelagia.local")).id;
  accountsId = (await getSeedUser("accounts@pelagia.local")).id;
  loginRankId = (await db.rank.findFirstOrThrow({ where: { grantsLogin: true } })).id;
  plainRankId = (await db.rank.findFirstOrThrow({ where: { grantsLogin: false } })).id;
  siteId = (await db.site.findFirstOrThrow()).id;
});

afterEach(async () => {
  await db.crewAction.deleteMany({});
  await db.ppeIssue.deleteMany({});
  await db.nextOfKin.deleteMany({});
  await db.crewAssignment.deleteMany({});
  await db.crewMember.deleteMany({});
  await db.user.deleteMany({ where: { email: LOGIN_EMAIL } });
  vi.clearAllMocks();
});

afterAll(async () => {
  await db.user.deleteMany({ where: { email: LOGIN_EMAIL } });
});

describe("SITE_STAFF login on placement (grantsLogin ranks)", () => {
  it("creates a SITE_STAFF login (with home site) for a management-rank placement", async () => {
    const c = await db.crewMember.create({ data: { name: "New PM", status: "CANDIDATE", type: "NEW", source: "WALK_IN", email: LOGIN_EMAIL } });
    as(managerId, "MANAGER");
    expect("ok" in (await placeCrew(fd({ crewMemberId: c.id, rankId: loginRankId, siteId, signOnDate: "2026-07-01" })))).toBe(true);

    const after = await db.crewMember.findUniqueOrThrow({ where: { id: c.id } });
    const login = await db.user.findUniqueOrThrow({ where: { email: LOGIN_EMAIL } });
    expect(login.role).toBe("SITE_STAFF");
    expect(login.employeeId).toBe(after.employeeId); // shares the CRW- number
    expect(login.passwordHash).toBeNull();
    expect(login.siteId).toBe(siteId); // own-site link set at creation
  });

  it("creates no login for a non-login rank", async () => {
    const c = await db.crewMember.create({ data: { name: "Deck Hand", status: "CANDIDATE", type: "NEW", source: "WALK_IN", email: LOGIN_EMAIL } });
    as(managerId, "MANAGER");
    await placeCrew(fd({ crewMemberId: c.id, rankId: plainRankId, siteId, signOnDate: "2026-07-01" }));
    expect(await db.user.findUnique({ where: { email: LOGIN_EMAIL } })).toBeNull();
  });

  it("skips the login when the crew member has no email (placement still succeeds)", async () => {
    const c = await db.crewMember.create({ data: { name: "No Email PM", status: "CANDIDATE", type: "NEW", source: "WALK_IN" } });
    as(managerId, "MANAGER");
    expect("ok" in (await placeCrew(fd({ crewMemberId: c.id, rankId: loginRankId, siteId, signOnDate: "2026-07-01" })))).toBe(true);
    expect((await db.crewMember.findUniqueOrThrow({ where: { id: c.id } })).status).toBe("EMPLOYEE");
  });
});

describe("PPE / next-of-kin verification (MPO)", () => {
  async function crewWithRecords() {
    const c = await db.crewMember.create({ data: { name: "Verify Me", status: "EMPLOYEE", type: "NEW", source: "CAREERS" } });
    const ppe = await db.ppeIssue.create({ data: { crewMemberId: c.id, item: "HELMET" } });
    const nok = await db.nextOfKin.create({ data: { crewMemberId: c.id, name: "Spouse" } });
    return { ppeId: ppe.id, nokId: nok.id };
  }

  it("MPO verifies PPE and next-of-kin", async () => {
    const { ppeId, nokId } = await crewWithRecords();
    as(manningId, "MANNING");
    expect("ok" in (await verifyPpe(ppeId, true))).toBe(true);
    expect((await db.ppeIssue.findUniqueOrThrow({ where: { id: ppeId } })).verificationStatus).toBe("VERIFIED");
    expect("ok" in (await verifyNextOfKin(nokId, true))).toBe(true);
    expect((await db.nextOfKin.findUniqueOrThrow({ where: { id: nokId } })).verificationStatus).toBe("VERIFIED");
  });

  it("rejection requires a reason; already-decided is guarded", async () => {
    const { ppeId } = await crewWithRecords();
    as(manningId, "MANNING");
    expect("error" in (await verifyPpe(ppeId, false))).toBe(true);
    expect("ok" in (await verifyPpe(ppeId, false, "Wrong size"))).toBe(true);
    expect("error" in (await verifyPpe(ppeId, true))).toBe(true); // already rejected
  });

  it("is rejected for roles without verify_site_records (accounts)", async () => {
    const { ppeId } = await crewWithRecords();
    as(accountsId, "ACCOUNTS");
    expect(await verifyPpe(ppeId, true)).toEqual({ error: "Unauthorized" });
  });
});
