import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  CloseReasonRequiredError,
  OpenQuantityRemainsError,
  PurchaseOrderNotClosableError,
  PurchaseOrderNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr } from "../testing/commandTestUtils";
import { basePurchaseOrder, basePurchaseOrderLine, baseReceiptEvidence } from "../testing/fixtures";
import {
  mockInventoryCommands,
  mockInventoryQueries,
  type ReceiptEvidence,
} from "../testing/moduleMocks";
import { run as runCommand } from "./closePurchaseOrder";

type KMock = ReturnType<typeof createKyselyMock<Namespace["main-db"]>>;

function run(
  mock: KMock,
  input: Parameters<typeof runCommand>[1],
  ctx: Parameters<typeof runCommand>[2],
  receiptEvidence: ReceiptEvidence[] = [],
  inventoryCommands: Parameters<typeof runCommand>[3] = mockInventoryCommands(),
) {
  return mock.withTx((trx) =>
    runCommand(trx, input, ctx, inventoryCommands, mockInventoryQueries({ receiptEvidence })),
  );
}

describe("closePurchaseOrder", () => {
  it("closes a fully billed purchase order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const billedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "RECEIVED",
      billingStatus: "BILLED",
    };
    const fullyBilledLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: billedOrder.id,
    };
    mock.enqueueResults(
      [billedOrder], // selectOne PurchaseOrder (forUpdate)
      [fullyBilledLine], // selectFrom PurchaseOrderLine
      [{ ...billedOrder, orderStatus: "CLOSED" }],
    );
    const result = await run(mock, { id: billedOrder.id }, commandCtx, [
      { ...baseReceiptEvidence, quantity: "10" },
    ]);
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("closes a partially received order when remaining quantity is explicitly written off", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    mock.enqueueResults(
      [partialOrder], // selectOne PurchaseOrder (forUpdate)
      [partialLine], // selectFrom PurchaseOrderLine
      [{ ...partialOrder, orderStatus: "CLOSED", closeReason: "Write off" }],
    );
    const result = await run(
      mock,
      { id: partialOrder.id, writeOffRemaining: true, closeReason: "Write off" },
      commandCtx,
      [baseReceiptEvidence],
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("returns error when purchase order does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);
    expectErr(await run(mock, { id: "missing-order" }, commandCtx), PurchaseOrderNotFoundError);
  });

  it("returns error when purchase order status does not allow closure", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePurchaseOrder]);
    expectErr(
      await run(mock, { id: basePurchaseOrder.id }, commandCtx),
      PurchaseOrderNotClosableError,
    );
  });

  it("returns error when unresolved open quantity remains", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    mock.enqueueResults(
      [partialOrder], // selectOne PurchaseOrder (forUpdate)
      [partialLine], // selectFrom PurchaseOrderLine
    );
    expectErr(
      await run(mock, { id: partialOrder.id }, commandCtx, [baseReceiptEvidence]),
      OpenQuantityRemainsError,
    );
  });

  it("returns error when residual quantity exists without a close reason", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    mock.enqueueResults(
      [partialOrder], // selectOne PurchaseOrder (forUpdate)
      [partialLine], // selectFrom PurchaseOrderLine
    );
    expectErr(
      await run(mock, { id: partialOrder.id, writeOffRemaining: true }, commandCtx, [
        baseReceiptEvidence,
      ]),
      CloseReasonRequiredError,
    );
  });

  it("allows closure without write-off when each line is fully received", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const receivedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "RECEIVED",
      billingStatus: "PARTIALLY_BILLED",
    };
    const fullyReceivedLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: receivedOrder.id,
    };
    mock.enqueueResults(
      [receivedOrder], // selectOne PurchaseOrder (forUpdate)
      [fullyReceivedLine], // selectFrom PurchaseOrderLine
      [{ ...receivedOrder, orderStatus: "CLOSED" }],
    );

    const result = await run(mock, { id: receivedOrder.id }, commandCtx, [
      { ...baseReceiptEvidence, quantity: "10" },
    ]);
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("returns OPEN_QUANTITY_REMAINS before requiring a close reason when write-off is not requested", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    mock.enqueueResults(
      [partialOrder], // selectOne PurchaseOrder (forUpdate)
      [partialLine], // selectFrom PurchaseOrderLine
    );

    expectErr(
      await run(mock, { id: partialOrder.id, closeReason: "Attempted close" }, commandCtx, [
        baseReceiptEvidence,
      ]),
      OpenQuantityRemainsError,
    );
  });

  it("closes an order with no lines (empty PO line IDs skip GR queries)", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const emptyOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "NOT_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    mock.enqueueResults(
      [emptyOrder], // selectOne PurchaseOrder (forUpdate)
      [], // PurchaseOrderLine (empty → GR/Bill queries skipped)
      [{ ...emptyOrder, orderStatus: "CLOSED" }],
    );

    const result = await run(mock, { id: emptyOrder.id }, commandCtx);
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("sums received quantities across multiple inventory receipt lines per PO line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
    };
    const line = {
      ...basePurchaseOrderLine,
      purchaseOrderId: orderedOrder.id,
      quantity: "10",
    };
    mock.enqueueResults(
      [orderedOrder], // selectOne PurchaseOrder (forUpdate)
      [line], // PurchaseOrderLine
      [{ ...orderedOrder, orderStatus: "CLOSED" }],
    );

    const result = await run(mock, { id: orderedOrder.id }, commandCtx, [
      { ...baseReceiptEvidence, quantity: "6" },
      baseReceiptEvidence,
    ]);
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("detects open quantity when only billed but not received", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
    };
    const line = {
      ...basePurchaseOrderLine,
      purchaseOrderId: orderedOrder.id,
      quantity: "10",
      billedQuantity: "10",
    };
    mock.enqueueResults(
      [orderedOrder], // selectOne PurchaseOrder (forUpdate)
      [line], // PurchaseOrderLine
      [{ ...orderedOrder, orderStatus: "CLOSED" }],
    );

    // received < quantity but billed >= quantity, so NOT hasUnresolvedOpenQuantity
    const result = await run(mock, { id: orderedOrder.id }, commandCtx);
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });
});
