import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateSupplierInvoiceNumberError,
  InvalidBilledQuantityError,
  PurchaseBillNotDraftError,
  PurchaseBillNotFoundError,
  PurchaseOrderCancelledError,
  PurchaseOrderLineNotFoundError,
} from "../lib/errors.generated";
import {
  basePurchaseBill,
  basePurchaseBillLines,
  basePurchaseOrder,
  blockedPurchaseBill,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./updatePurchaseBill";

describe("updatePurchaseBill", () => {
  it("updates invoice metadata on a draft purchase bill", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = { ...basePurchaseBill, invoiceNumber: "INV-UPDATED" };
    spies.select
      .mockReturnValueOnce(basePurchaseBill)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder]);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: basePurchaseBill.id, invoiceNumber: "INV-UPDATED" },
      commandCtx,
    );

    expect(expectOk(result).purchaseBill.invoiceNumber).toBe("INV-UPDATED");
    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({ invoiceNumber: "INV-UPDATED" }),
    );
  });

  it("updates bill lines on a draft purchase bill", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = {
      ...basePurchaseBill,
      lines: JSON.stringify([{ ...basePurchaseBillLines[0], quantity: 5 }]),
    };
    spies.select.mockReturnValueOnce(basePurchaseBill).mockReturnValueOnce([basePurchaseOrder]);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: basePurchaseBill.id, lines: [{ ...basePurchaseBillLines[0], quantity: 5 }] },
      commandCtx,
    );

    expect(expectOk(result).purchaseBill.lines).toBe(updated.lines);
    const setCall = spies.set.mock.calls[0]?.[0] as { lines?: string };
    expect(setCall.lines).toContain('"quantity":5');
  });

  it("returns error when purchase bill does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, { id: "missing-bill" }, commandCtx);

    expectErr(result, PurchaseBillNotFoundError);
  });

  it("returns error when purchase bill is not draft", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(blockedPurchaseBill);

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

    expectErr(result, PurchaseBillNotDraftError);
  });

  it("returns error when updated invoice number is duplicated", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseBill).mockReturnValueOnce({ id: "existing-bill" });

    const result = await run(
      db,
      { id: basePurchaseBill.id, invoiceNumber: "INV-DUPLICATE" },
      commandCtx,
    );

    expectErr(result, DuplicateSupplierInvoiceNumberError);
  });

  it("returns error when updated line data is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseBill).mockReturnValueOnce([basePurchaseOrder]);

    const result = await run(
      db,
      {
        id: basePurchaseBill.id,
        lines: [{ ...basePurchaseBillLines[0], purchaseOrderLineId: "missing-line" }],
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderLineNotFoundError);
  });

  it("returns error when updated billed quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseBill).mockReturnValueOnce([basePurchaseOrder]);

    const result = await run(
      db,
      { id: basePurchaseBill.id, lines: [{ ...basePurchaseBillLines[0], quantity: 0 }] },
      commandCtx,
    );

    expectErr(result, InvalidBilledQuantityError);
  });

  it("returns error when updated bill references a cancelled purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseBill)
      .mockReturnValueOnce([{ ...basePurchaseOrder, status: "CANCELLED" }]);

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

    expectErr(result, PurchaseOrderCancelledError);
  });
});
