import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PaymentTermInactiveError,
  PaymentTermNotFoundError,
  PurchaseOrderNotDraftError,
  PurchaseOrderNotFoundError,
  ReceivingSiteNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk, mockItemQueries } from "../testing/commandTestUtils";
import {
  baseItem,
  basePaymentTerm,
  basePaymentTermLine,
  basePurchaseOrder,
  basePurchaseOrderLines,
  baseUnit,
  inactivePaymentTerm,
  inactiveItem,
} from "../testing/fixtures";
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
      .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("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 header receiving site does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined); // selectOne Site not found
    expectErr(
      await run(
        db,
        { id: basePurchaseOrder.id, receivingSiteId: "missing-site" },
        commandCtx,
        itemQ,
      ),
      ReceivingSiteNotFoundError,
    );
  });

  it("returns error when line receiving site does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder).mockReturnValueOnce(undefined); // selectOne Site (line receivingSiteId check)

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

  it("inserts lines with receivingSiteId as null when not provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder) // PurchaseOrder
      .mockReturnValueOnce(basePaymentTerm) // selectOne PurchasePaymentTerm
      .mockReturnValueOnce([basePaymentTermLine]); // execute PurchasePaymentTermLine
    spies.update.mockReturnValueOnce(basePurchaseOrder);

    await run(
      db,
      {
        id: basePurchaseOrder.id,
        lines: [
          {
            itemId: baseItem.id,
            quantity: "10",
            unitPrice: "100",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      itemQ,
    );

    const valuesCall = spies.values.mock.calls[0]?.[0] as Record<string, unknown>[];
    expect(valuesCall[0]).toEqual(
      expect.objectContaining({
        itemSnapshotName: baseItem.name,
        itemSnapshotSku: baseItem.sku,
        receivingSiteId: null,
        requiresPhysicalReceipt: true,
      }),
    );
  });

  it("returns error when selected payment term does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined); // selectOne PurchasePaymentTerm not found
    expectErr(
      await run(
        db,
        { id: basePurchaseOrder.id, paymentTermId: "missing-payment-term" },
        commandCtx,
        itemQ,
      ),
      PaymentTermNotFoundError,
    );
  });

  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,
    );
  });
});
