/**
 * Integration tests for the Ranks & Documents admin server actions (Crewing
 * Phase 1 foundations). Covers create/update/delete, parent linking, the
 * cycle/children guards, doc-requirement add/remove, and permission gating.
 */
import { vi, describe, it, expect, beforeAll, afterEach } from "vitest";

vi.mock("@/auth", () => ({ auth: vi.fn() }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
// The actions are gated by the crewing flag; force it on for the test run.
vi.mock("@/lib/feature-flags", () => ({ CREWING_ENABLED: true, INVENTORY_ENABLED: true }));

import { auth } from "@/auth";
import { db } from "@/lib/db";
import {
  createRank,
  updateRank,
  deleteRank,
  addRankDocRequirement,
  removeRankDocRequirement,
} from "@/app/(portal)/admin/ranks/actions";
import { makeSession, getSeedUser, fd } from "./helpers";

const PREFIX = "ITRANK_";
let managerId: string;

const asManager = () =>
  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));

beforeAll(async () => {
  const mgr = await getSeedUser("manager@pelagia.local");
  managerId = mgr.id;
});

afterEach(async () => {
  // Break self-relations before deleting so parent/child FK order never bites.
  const rows = await db.rank.findMany({ where: { code: { startsWith: PREFIX } }, select: { id: true } });
  const ids = rows.map((r) => r.id);
  if (ids.length) {
    await db.rankDocRequirement.deleteMany({ where: { rankId: { in: ids } } });
    await db.rank.updateMany({ where: { id: { in: ids } }, data: { parentId: null } });
    await db.rank.deleteMany({ where: { id: { in: ids } } });
  }
  vi.clearAllMocks();
});

describe("createRank", () => {
  it("creates a rank with its flags", async () => {
    asManager();
    const res = await createRank(
      fd({ code: `${PREFIX}PM`, name: "Test PM", category: "OPERATIONAL", grantsLogin: "on" })
    );
    expect(res).toEqual({ ok: true });

    const rank = await db.rank.findUnique({ where: { code: `${PREFIX}PM` } });
    expect(rank?.grantsLogin).toBe(true);
    expect(rank?.isSeafarer).toBe(false);
    expect(rank?.category).toBe("OPERATIONAL");
  });

  it("rejects a duplicate code", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}DUP`, name: "One", category: "SUPPORT" }));
    const res = await createRank(fd({ code: `${PREFIX}DUP`, name: "Two", category: "SUPPORT" }));
    expect("error" in res).toBe(true);
  });

  it("is rejected for roles without manage_ranks", async () => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "SITE_STAFF"));
    const res = await createRank(fd({ code: `${PREFIX}NO`, name: "Nope", category: "OPERATIONAL" }));
    expect(res).toEqual({ error: "Unauthorized" });
    expect(await db.rank.findUnique({ where: { code: `${PREFIX}NO` } })).toBeNull();
  });
});

describe("updateRank — parent linking & cycle guard", () => {
  it("links a child to its parent", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}P`, name: "Parent", category: "OPERATIONAL" }));
    const parent = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}P` } });
    await createRank(fd({ code: `${PREFIX}C`, name: "Child", category: "OPERATIONAL" }));
    const child = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}C` } });

    const res = await updateRank(
      fd({ id: child.id, code: `${PREFIX}C`, name: "Child", category: "OPERATIONAL", parentId: parent.id })
    );
    expect(res).toEqual({ ok: true });
    expect((await db.rank.findUnique({ where: { id: child.id } }))?.parentId).toBe(parent.id);
  });

  it("refuses to make a rank its own ancestor", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}A`, name: "A", category: "OPERATIONAL" }));
    const a = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}A` } });
    await createRank(fd({ code: `${PREFIX}B`, name: "B", category: "OPERATIONAL", parentId: a.id }));
    const b = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}B` } });

    // Try to make A report to B (its own descendant) → cycle.
    const res = await updateRank(fd({ id: a.id, code: `${PREFIX}A`, name: "A", category: "OPERATIONAL", parentId: b.id }));
    expect("error" in res).toBe(true);
  });
});

describe("deleteRank", () => {
  it("blocks deletion when the rank has sub-ranks", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}TOP`, name: "Top", category: "OPERATIONAL" }));
    const top = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}TOP` } });
    await createRank(fd({ code: `${PREFIX}SUB`, name: "Sub", category: "OPERATIONAL", parentId: top.id }));

    const res = await deleteRank(top.id);
    expect("error" in res).toBe(true);
    expect(await db.rank.findUnique({ where: { id: top.id } })).not.toBeNull();
  });

  it("deletes a leaf rank and cascades its doc requirements", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}LEAF`, name: "Leaf", category: "OPERATIONAL" }));
    const leaf = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}LEAF` } });
    await addRankDocRequirement(fd({ rankId: leaf.id, docType: "PASSPORT", isMandatory: "on" }));

    const res = await deleteRank(leaf.id);
    expect(res).toEqual({ ok: true });
    expect(await db.rankDocRequirement.findMany({ where: { rankId: leaf.id } })).toHaveLength(0);
  });
});

describe("rank document requirements", () => {
  it("adds (upserting) and removes a requirement", async () => {
    asManager();
    await createRank(fd({ code: `${PREFIX}DOC`, name: "Doc", category: "OPERATIONAL", isSeafarer: "on" }));
    const rank = await db.rank.findUniqueOrThrow({ where: { code: `${PREFIX}DOC` } });

    await addRankDocRequirement(fd({ rankId: rank.id, docType: "STCW", isMandatory: "on" }));
    // Upsert: same docType again flips it to conditional rather than duplicating.
    await addRankDocRequirement(fd({ rankId: rank.id, docType: "STCW", isMandatory: "false" }));
    const reqs = await db.rankDocRequirement.findMany({ where: { rankId: rank.id } });
    expect(reqs).toHaveLength(1);
    expect(reqs[0].isMandatory).toBe(false);

    const rm = await removeRankDocRequirement(reqs[0].id);
    expect(rm).toEqual({ ok: true });
    expect(await db.rankDocRequirement.findMany({ where: { rankId: rank.id } })).toHaveLength(0);
  });
});
