import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it, vi } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  ItemNotActiveError,
  PartnerNotSupplierError,
  PurchaseOrderNotFoundError,
  PurchaseOrderNotSubmittedError,
  SupplierNotActiveError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseItem,
  basePurchaseOrderLine,
  basePurchaseOrder,
  baseSupplier,
  baseSupplierRole,
  submittedPurchaseOrder,
} from "../testing/fixtures";
import { mockBPQueries, mockInventoryCommands, mockItemQueries } from "../testing/moduleMocks";
import { run as runCommand } from "./approvePurchaseOrder";

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

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

describe("approvePurchaseOrder", () => {
  it("approves a valid submitted purchase order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const line = { ...basePurchaseOrderLine, purchaseOrderId: submittedPurchaseOrder.id };
    const bpQ = mockBPQueries({
      partner: baseSupplier,
      partnerRole: baseSupplierRole,
    });
    const itemQ = mockItemQueries({ item: baseItem });
    mock.enqueueResults(
      [submittedPurchaseOrder], // selectOne PurchaseOrder
      [line], // selectFrom PurchaseOrderLine (execute → array)
      [
        {
          ...submittedPurchaseOrder,
          orderStatus: "ORDERED",
          receiptStatus: "NOT_RECEIVED",
          billingStatus: "NOT_BILLED",
        },
      ],
    );

    const result = await run(mock, { id: submittedPurchaseOrder.id }, commandCtx, bpQ, itemQ);

    expect(expectOk(result).purchaseOrder.orderStatus).toBe("ORDERED");
    expect(expectOk(result).purchaseOrder.receiptStatus).toBe("NOT_RECEIVED");
    expect(expectOk(result).purchaseOrder.billingStatus).toBe("NOT_BILLED");
  });

  it("creates inventory supply plans for physical receipt lines on approval", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const line = { ...basePurchaseOrderLine, purchaseOrderId: submittedPurchaseOrder.id };
    const bpQ = mockBPQueries({
      partner: baseSupplier,
      partnerRole: baseSupplierRole,
    });
    const itemQ = mockItemQueries({ item: baseItem });
    const inventoryCommands = mockInventoryCommands();
    const createSupplySpy = vi.fn(inventoryCommands.createInventorySupplyPlan);
    mock.enqueueResults(
      [submittedPurchaseOrder],
      [line],
      [
        {
          ...submittedPurchaseOrder,
          orderStatus: "ORDERED",
          receiptStatus: "NOT_RECEIVED",
          billingStatus: "NOT_BILLED",
        },
      ],
    );

    const result = await run(mock, { id: submittedPurchaseOrder.id }, commandCtx, bpQ, itemQ, {
      ...inventoryCommands,
      createInventorySupplyPlan: createSupplySpy,
    });

    expectOk(result);
    expect(createSupplySpy).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({
        sourceType: "PURCHASE_ORDER",
        sourceId: submittedPurchaseOrder.id,
        supplyPlans: [
          expect.objectContaining({
            sourceLineId: line.id,
            itemId: line.itemId,
            siteId: line.receivingSiteId,
            expectedQuantity: line.quantity,
            unitId: line.unitId,
          }),
        ],
      }),
      commandCtx,
    );
  });

  it("returns error when purchase order does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await run(
      mock,
      { id: "missing-order" },
      commandCtx,
      mockBPQueries(),
      mockItemQueries(),
    );

    expectErr(result, PurchaseOrderNotFoundError);
  });

  it("returns error when purchase order is not submitted", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePurchaseOrder]);

    const result = await run(
      mock,
      { id: basePurchaseOrder.id },
      commandCtx,
      mockBPQueries(),
      mockItemQueries(),
    );

    expectErr(result, PurchaseOrderNotSubmittedError);
  });

  it("returns error when supplier is inactive", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const bpQ = mockBPQueries({
      partner: { ...baseSupplier, status: "INACTIVE" },
    });
    const itemQ = mockItemQueries();
    mock.enqueueResults([submittedPurchaseOrder]);

    const result = await run(mock, { id: submittedPurchaseOrder.id }, commandCtx, bpQ, itemQ);

    expectErr(result, SupplierNotActiveError);
  });

  it("returns error when an item is inactive", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const line = { ...basePurchaseOrderLine, purchaseOrderId: submittedPurchaseOrder.id };
    const bpQ = mockBPQueries({
      partner: baseSupplier,
      partnerRole: baseSupplierRole,
    });
    const itemQ = mockItemQueries({ item: { ...baseItem, status: "INACTIVE" } });
    mock.enqueueResults(
      [submittedPurchaseOrder], // selectOne PurchaseOrder
      [line], // selectFrom PurchaseOrderLine (execute → array)
    );

    const result = await run(mock, { id: submittedPurchaseOrder.id }, commandCtx, bpQ, itemQ);

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when supplier is no longer classified as a supplier", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const bpQ = mockBPQueries({
      partner: baseSupplier,
    });
    const itemQ = mockItemQueries();
    mock.enqueueResults([submittedPurchaseOrder]);

    const result = await run(mock, { id: submittedPurchaseOrder.id }, commandCtx, bpQ, itemQ);

    expectErr(result, PartnerNotSupplierError);
  });
});
