/**
 * Integration tests for accounts payment server actions.
 * Covers: A-01 (payment queue — PO reaches MGR_APPROVED), A-02 (mark paid with reference number).
 */
import { vi, describe, it, expect, beforeAll, 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 } from "@/app/(portal)/approvals/[id]/actions";
import { processPayment, markPaid } from "@/app/(portal)/payments/actions";
import {
  makeSession, getSeedUser, getSeedVessel, getSeedAccount, getSeedVendor,
  makePoForm, deletePosByTitle,
} from "./helpers";

const PREFIX = "INTTEST_PAYMENT_";
const TODAY = new Date().toISOString().slice(0, 10); // yyyy-mm-dd, used for payment date
let techId: string;
let managerId: string;
let accountsId: string;
let vesselId: string;
let accountId: string;
let vendorId: 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 Thetis"),
    getSeedAccount("700202"),
    getSeedVendor("Apar Industries Ltd"),
  ]);
  techId = tech.id;
  managerId = mgr.id;
  accountsId = acct.id;
  vesselId = vessel.id;
  accountId = account.id;
  vendorId = vendor.id;
});

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

// Helper: create PO → submit → approve (reaches MGR_APPROVED)
async function createApprovedPo(title: string): Promise<string> {
  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
  const form = makePoForm({ title, vesselId, accountId, intent: "submit" });
  const { id: poId } = (await createPo(form)) as { id: string };
  // Vendor gating: approval requires an assigned vendor.
  await db.purchaseOrder.update({ where: { id: poId }, data: { vendorId } });

  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));
  await approvePo({ poId });
  return poId;
}

// ── A-01: PO reaches payment queue (MGR_APPROVED) ───────────────────────────

describe("A-01 — approved PO appears in payment queue", () => {
  it("PO has status MGR_APPROVED after manager approves", async () => {
    const poId = await createApprovedPo(`${PREFIX}Queue`);
    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("MGR_APPROVED");
  });

  it("processPayment transitions MGR_APPROVED to SENT_FOR_PAYMENT", async () => {
    const poId = await createApprovedPo(`${PREFIX}ProcessPayment`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));

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

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

  it("TECHNICAL role cannot process payment", async () => {
    const poId = await createApprovedPo(`${PREFIX}PaymentForbidden`);
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
    const result = await processPayment({ poId });
    expect(result).toHaveProperty("error");
  });
});

// ── A-02: Mark as paid with reference number ─────────────────────────────────

describe("A-02 — mark PO as paid with reference number", () => {
  it("transitions SENT_FOR_PAYMENT to PAID_DELIVERED and stores paymentRef", async () => {
    const poId = await createApprovedPo(`${PREFIX}MarkPaid`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    await processPayment({ poId });
    const result = await markPaid({ poId, paymentRef: "NEFT/2026/001234", paymentDate: TODAY });
    expect(result).toEqual({ ok: true });

    const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
    expect(po?.status).toBe("PAID_DELIVERED");
    expect(po?.paymentRef).toBe("NEFT/2026/001234");
    expect(po?.paidAt).not.toBeNull();
    expect(po?.paymentDate).not.toBeNull();
  });

  it("creates a PAYMENT_SENT action in the audit trail", async () => {
    const poId = await createApprovedPo(`${PREFIX}PaidAudit`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    await processPayment({ poId });
    await markPaid({ poId, paymentRef: "TXN-9999", paymentDate: TODAY });

    const action = await db.pOAction.findFirst({ where: { poId, actionType: "PAYMENT_SENT" } });
    expect(action).not.toBeNull();
    expect((action?.metadata as { paymentRef?: string })?.paymentRef).toBe("TXN-9999");
  });

  it("returns error when paymentRef is missing", async () => {
    const poId = await createApprovedPo(`${PREFIX}PaidNoRef`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    await processPayment({ poId });
    const result = await markPaid({ poId, paymentRef: "", paymentDate: TODAY });
    expect(result).toHaveProperty("error");
  });

  it("returns error when payment date is in the future", async () => {
    const poId = await createApprovedPo(`${PREFIX}PaidFutureDate`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    await processPayment({ poId });
    const future = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
    const result = await markPaid({ poId, paymentRef: "FUTURE-REF", paymentDate: future });
    expect(result).toHaveProperty("error");
  });

  it("notifies submitter and managers when payment is marked", async () => {
    const { notify } = await import("@/lib/notifier");
    const poId = await createApprovedPo(`${PREFIX}PaidNotify`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    vi.mocked(notify).mockClear();
    await processPayment({ poId });
    await markPaid({ poId, paymentRef: "REF-42", paymentDate: TODAY });

    const calls = vi.mocked(notify).mock.calls.map((c) => c[0].event);
    expect(calls).toContain("PAYMENT_SENT");
  });

  it("TECHNICAL role cannot mark as paid (no process_payment permission)", async () => {
    const poId = await createApprovedPo(`${PREFIX}PaidTechForbidden`);

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    await processPayment({ poId });

    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
    const result = await markPaid({ poId, paymentRef: "TECH-REF", paymentDate: TODAY });
    expect(result).toHaveProperty("error");
  });
});
