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,
  basePaymentTermLine,
  basePurchaseOrder,
  basePurchaseOrderLines,
  baseReceivingSite,
  inactivePaymentTerm,
  inactiveItem,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk, mockItemQueries } from "../testing/commandTestUtils";
import { run } from "./updatePurchaseOrder";

const itemQ = mockItemQueries({ item: baseItem });

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) // selectOne PurchaseOrder
      .mockReturnValueOnce(basePaymentTerm) // selectOne PurchasePaymentTerm (existing paymentTermId)
      .mockReturnValueOnce([basePaymentTermLine]); // execute PurchasePaymentTermLine
    spies.update.mockReturnValueOnce(updated);

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

    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) // selectOne PurchaseOrder
      .mockReturnValueOnce(baseReceivingSite) // selectOne Site
      .mockReturnValueOnce(basePaymentTerm) // selectOne PurchasePaymentTerm
      .mockReturnValueOnce([basePaymentTermLine]); // execute PurchasePaymentTermLine
    spies.update.mockReturnValueOnce(basePurchaseOrder);

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

    // The inserted PurchaseOrderLine values should contain the matchedPriceRuleId
    const valuesCall = spies.values.mock.calls[0]?.[0] as { matchedPriceRuleId?: string }[];
    expect(valuesCall[0]?.matchedPriceRuleId).toBe("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, itemQ),
      PurchaseOrderNotFoundError,
    );
  });

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

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

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

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

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

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

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

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

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

    expectErr(
      await run(
        db,
        {
          id: basePurchaseOrder.id,
          lines: [{ ...basePurchaseOrderLines[0], receivingSiteId: "missing-site" }],
        },
        commandCtx,
        itemQ,
      ),
      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,
    });

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

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