/**
 * Integration tests for vendor-gated approval and provide-vendor-id.
 * Covers:
 *   - Approval blocked when no vendor assigned
 *   - Approval succeeds once vendor is set
 *   - ACCOUNTS role can now call provideVendorId
 *   - Unverified vendor rejected by provideVendorId
 *   - AUDITOR cannot provide vendor ID
 */
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/notifier", () => ({ notify: vi.fn() }));

import { auth } from "@/auth";
import { db } from "@/lib/db";
import { createPo } from "@/app/(portal)/po/new/actions";
import { approvePo, requestVendorId } from "@/app/(portal)/approvals/[id]/actions";
import { provideVendorId } from "@/app/(portal)/po/[id]/actions";
import {
  makeSession, getSeedUser, getSeedVessel, getSeedAccount, getSeedVendor,
  makePoForm, deletePosByTitle,
} from "./helpers";

const PREFIX = "INTTEST_VENDOR_APPROVAL_";
let techId: string;
let managerId: string;
let accountsId: string;
let auditorId: string;
let vesselId: string;
let accountId: string;
let verifiedVendorId: string;
let unverifiedVendorDbId: string;

beforeAll(async () => {
  const [tech, mgr, acct, vessel, account, vendor] = await Promise.all([
    getSeedUser("tech@pelagia.local"),
    getSeedUser("manager@pelagia.local"),
    getSeedUser("accounts@pelagia.local"),
    getSeedVessel("MV Pelagia Star"),
    getSeedAccount("700201"),
    getSeedVendor("Apar Industries Ltd"),
  ]);
  techId = tech.id;
  managerId = mgr.id;
  accountsId = acct.id;
  vesselId = vessel.id;
  accountId = account.id;
  verifiedVendorId = vendor.id;

  // Auditor — create on-the-fly if not seeded
  const maybeAuditor = await db.user.findFirst({ where: { role: "AUDITOR" } });
  if (maybeAuditor) {
    auditorId = maybeAuditor.id;
  } else {
    const created = await db.user.create({
      data: {
        employeeId: "EMP-TEST-AUD",
        email: "auditor@test.local",
        name: "Test Auditor",
        passwordHash: "irrelevant",
        role: "AUDITOR",
      },
    });
    auditorId = created.id;
  }

  // A vendor with no formal vendorId code — provideVendorId must reject it.
  // (Seeded "unverified" vendors can still carry a code, so create a code-less one.)
  const noCode = await db.vendor.create({
    data: { name: `${PREFIX}NoCodeVendor`, isVerified: false, vendorId: null },
  });
  unverifiedVendorDbId = noCode.id;
});

afterEach(async () => {
  await deletePosByTitle(PREFIX);
});

afterAll(async () => {
  await db.vendor.deleteMany({ where: { name: { startsWith: PREFIX } } });
});

async function makeReviewPo(title: string, withVendor = false) {
  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
  const form = makePoForm({
    title,
    vesselId,
    accountId,
    intent: "submit",
    vendorId: withVendor ? verifiedVendorId : undefined,
  });
  const result = await createPo(form);
  return (result as { id: string }).id;
}

// ── Vendor required for approval ──────────────────────────────────────────────

describe("approval — vendor required", () => {
  it("blocks approval when PO has no vendor assigned", async () => {
    const poId = await makeReviewPo(`${PREFIX}NoVendorBlock`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));

    const result = await approvePo({ poId });
    expect(result).toHaveProperty("error");
    expect((result as { error: string }).error).toMatch(/vendor/i);

    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("MGR_REVIEW");
  });

  it("allows approval when PO has a vendor assigned", async () => {
    const poId = await makeReviewPo(`${PREFIX}VendorPresent`, true);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));

    const result = await approvePo({ poId });
    expect(result).toEqual({ ok: true });

    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("MGR_APPROVED");
  });
});

// ── provideVendorId — role expansion ─────────────────────────────────────────

describe("provideVendorId — role expansion", () => {
  async function makePendingPo(title: string) {
    const poId = await makeReviewPo(title);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));
    await requestVendorId({ poId });
    return poId;
  }

  it("ACCOUNTS can provide a verified vendor ID", async () => {
    const poId = await makePendingPo(`${PREFIX}AccountsProvide`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));

    const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
    expect(result).toEqual({ ok: true });

    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("MGR_REVIEW");
    expect(po?.vendorId).toBe(verifiedVendorId);
  });

  it("rejects an unverified vendor (no vendorId field on Vendor record)", async () => {
    const poId = await makePendingPo(`${PREFIX}UnverifiedVendor`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));

    const result = await provideVendorId({ poId, vendorId: unverifiedVendorDbId });
    expect(result).toHaveProperty("error");

    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("VENDOR_ID_PENDING");
  });

  it("AUDITOR cannot provide vendor ID", async () => {
    const poId = await makePendingPo(`${PREFIX}AuditorDenied`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(auditorId, "AUDITOR"));

    const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
    expect(result).toHaveProperty("error");
  });

  it("returns error when called on a PO not in VENDOR_ID_PENDING state", async () => {
    // PO still in MGR_REVIEW — no requestVendorId called
    const poId = await makeReviewPo(`${PREFIX}WrongState`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));

    const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
    expect(result).toHaveProperty("error");
  });
});
