import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DownstreamHandoffAcceptedError,
  PurchaseBillNotCancellableError,
  PurchaseBillNotFoundError,
} from "../lib/errors.generated";
import {
  basePurchaseBill,
  basePurchaseOrderLines,
  blockedPurchaseBill,
  matchedPurchaseBill,
  orderedPurchaseOrder,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./cancelPurchaseBill";

describe("cancelPurchaseBill", () => {
  it("cancels a draft purchase bill", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseBill);
    spies.update.mockReturnValueOnce({ ...basePurchaseBill, status: "CANCELLED" });
    expectOk(await run(db, { id: basePurchaseBill.id }, commandCtx));
  });

  it("cancels a blocked purchase bill", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(blockedPurchaseBill);
    spies.update.mockReturnValueOnce({ ...blockedPurchaseBill, status: "CANCELLED" });
    expectOk(await run(db, { id: blockedPurchaseBill.id }, commandCtx));
  });

  it("cancels a matched purchase bill before downstream acceptance", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const order = {
      ...orderedPurchaseOrder,
      lines: JSON.stringify([{ ...basePurchaseOrderLines[0], billedQuantity: 4 }]),
    };
    spies.select.mockReturnValueOnce(matchedPurchaseBill).mockReturnValueOnce([order]);
    spies.update
      .mockReturnValueOnce({ ...order })
      .mockReturnValueOnce({ ...matchedPurchaseBill, status: "CANCELLED" });

    const result = await run(db, { id: matchedPurchaseBill.id }, commandCtx);

    expect(expectOk(result).purchaseBill.status).toBe("CANCELLED");
  });

  it("preserves variance evidence when a blocked bill is cancelled", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(blockedPurchaseBill);
    spies.update.mockReturnValueOnce({ ...blockedPurchaseBill, status: "CANCELLED" });

    await run(db, { id: blockedPurchaseBill.id }, commandCtx);

    const setCall = spies.set.mock.calls[0]?.[0] as Record<string, unknown>;
    expect(setCall).not.toHaveProperty("varianceReason");
  });

  it("reverses purchase-order billed quantity when a matched bill is cancelled", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const order = {
      ...orderedPurchaseOrder,
      lines: JSON.stringify([{ ...basePurchaseOrderLines[0], billedQuantity: 4 }]),
    };
    spies.select.mockReturnValueOnce(matchedPurchaseBill).mockReturnValueOnce([order]);
    spies.update
      .mockReturnValueOnce(order)
      .mockReturnValueOnce({ ...matchedPurchaseBill, status: "CANCELLED" });

    await run(db, { id: matchedPurchaseBill.id }, commandCtx);

    const setCall = spies.set.mock.calls[0]?.[0] as { lines?: string };
    expect(setCall.lines).toContain('"billedQuantity":0');
  });

  it("returns error when purchase bill does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);
    expectErr(await run(db, { id: "missing-bill" }, commandCtx), PurchaseBillNotFoundError);
  });

  it("returns error when purchase bill status does not allow cancellation", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...basePurchaseBill, status: "CANCELLED" });
    expectErr(
      await run(db, { id: basePurchaseBill.id }, commandCtx),
      PurchaseBillNotCancellableError,
    );
  });

  it("returns error when downstream handoff has already been accepted", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...matchedPurchaseBill, downstreamAccepted: true });
    expectErr(
      await run(db, { id: matchedPurchaseBill.id }, commandCtx),
      DownstreamHandoffAcceptedError,
    );
  });
});
