import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PaymentTermInactiveError,
  PurchaseOrderNotDraftError,
  PurchaseOrderNotFoundError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
} from "../lib/errors.generated";
import {
  baseItem,
  basePaymentTerm,
  basePurchaseOrder,
  basePurchaseOrderLines,
  baseReceivingSite,
  inactivePaymentTerm,
  inactiveItem,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./updatePurchaseOrder";

describe("updatePurchaseOrder", () => {
  it("updates supplier-facing data on a draft purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = { ...basePurchaseOrder, externalSupplierOrderReference: "PO-EXT-2" };
    spies.select
      .mockReturnValueOnce(basePurchaseOrder)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseReceivingSite)
      .mockReturnValueOnce(basePaymentTerm);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: basePurchaseOrder.id, externalSupplierOrderReference: "PO-EXT-2" },
      commandCtx,
    );

    expect(expectOk(result).purchaseOrder.externalSupplierOrderReference).toBe("PO-EXT-2");
  });

  it("updates line pricing while preserving matched price-rule reference", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseReceivingSite)
      .mockReturnValueOnce(basePaymentTerm);
    spies.update.mockReturnValueOnce(basePurchaseOrder);

    await run(
      db,
      { id: basePurchaseOrder.id, lines: [{ ...basePurchaseOrderLines[0], unitPrice: 110 }] },
      commandCtx,
    );

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

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

  it("returns error when purchase order is not in draft", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...basePurchaseOrder, status: "SUBMITTED" });
    expectErr(await run(db, { id: basePurchaseOrder.id }, commandCtx), PurchaseOrderNotDraftError);
  });

  it("returns error when updated line data is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder).mockReturnValueOnce(undefined);
    expectErr(
      await run(
        db,
        {
          id: basePurchaseOrder.id,
          lines: [{ ...basePurchaseOrderLines[0], itemId: "missing-item" }],
        },
        commandCtx,
      ),
      ItemNotFoundError,
    );
  });

  it("returns error when an updated line item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder).mockReturnValueOnce(inactiveItem);

    expectErr(
      await run(
        db,
        {
          id: basePurchaseOrder.id,
          lines: [{ ...basePurchaseOrderLines[0], itemId: inactiveItem.id }],
        },
        commandCtx,
      ),
      ItemNotActiveError,
    );
  });

  it("returns error when an updated line quantity is not positive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder).mockReturnValueOnce(baseItem);

    expectErr(
      await run(
        db,
        { id: basePurchaseOrder.id, lines: [{ ...basePurchaseOrderLines[0], quantity: 0 }] },
        commandCtx,
      ),
      InvalidQuantityError,
    );
  });

  it("returns error when an updated line unit price is negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder).mockReturnValueOnce(baseItem);

    expectErr(
      await run(
        db,
        { id: basePurchaseOrder.id, lines: [{ ...basePurchaseOrderLines[0], unitPrice: -1 }] },
        commandCtx,
      ),
      InvalidUnitPriceError,
    );
  });

  it("returns error when an updated receiving site does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(undefined);

    expectErr(
      await run(
        db,
        {
          id: basePurchaseOrder.id,
          lines: [{ ...basePurchaseOrderLines[0], receivingSiteId: "missing-site" }],
        },
        commandCtx,
      ),
      ReceivingSiteNotFoundError,
    );
  });

  it("returns error when a physical-receipt line has no receiving site", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce({
        ...basePurchaseOrder,
        documentReceivingSiteId: null,
        lines: JSON.stringify([{ ...basePurchaseOrderLines[0], receivingSiteId: null }]),
      })
      .mockReturnValueOnce(baseItem);

    expectErr(
      await run(
        db,
        {
          id: basePurchaseOrder.id,
          documentReceivingSiteId: null,
          lines: [{ ...basePurchaseOrderLines[0], receivingSiteId: null }],
        },
        commandCtx,
      ),
      ReceivingSiteRequiredError,
    );
  });

  it("returns error when selected payment term is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseReceivingSite)
      .mockReturnValueOnce(inactivePaymentTerm);
    expectErr(
      await run(db, { id: basePurchaseOrder.id, paymentTermId: basePaymentTerm.id }, commandCtx),
      PaymentTermInactiveError,
    );
  });
});
