import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { DB } from "../generated/kysely-tailordb";
import { expectOk } from "../testing/commandTestUtils";
import {
  baseGoodsReceiptLine,
  basePurchaseBillLine,
  basePurchaseOrder,
  basePurchaseOrderLine,
  matchedPurchaseBill,
  orderedPurchaseOrderWithRevision,
  poLineForAmendment,
  postedGoodsReceipt,
} from "../testing/fixtures";
import { run } from "./getPurchaseOrder";

/** Mock the 4 fulfillment queries (GoodsReceiptLine, PurchaseBillLine, PurchaseBill, GoodsReceipt) with empty arrays */
function mockEmptyFulfillment(spies: { select: { mockReturnValueOnce: (v: unknown) => void } }) {
  spies.select.mockReturnValueOnce([]); // GoodsReceiptLine
  spies.select.mockReturnValueOnce([]); // PurchaseBillLine
  spies.select.mockReturnValueOnce([]); // PurchaseBill (MATCHED)
  spies.select.mockReturnValueOnce([]); // GoodsReceipt (POSTED)
}

describe("getPurchaseOrder", () => {
  it("returns purchase order when found by id", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    mockEmptyFulfillment(spies);

    const result = await run(db, { id: basePurchaseOrder.id });
    const { purchaseOrder } = expectOk<{ purchaseOrder: unknown }>(result as never);

    expect(purchaseOrder).not.toBeNull();
    expect(purchaseOrder).toMatchObject({ id: basePurchaseOrder.id });
  });

  it("returns purchase order when found by company and external supplier order reference", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    mockEmptyFulfillment(spies);

    const result = await run(db, {
      companyId: basePurchaseOrder.companyId,
      externalSupplierOrderReference: basePurchaseOrder.externalSupplierOrderReference,
    });
    const { purchaseOrder } = expectOk<{ purchaseOrder: unknown }>(result as never);

    expect(purchaseOrder).not.toBeNull();
    expect(purchaseOrder).toMatchObject({ id: basePurchaseOrder.id });
  });

  it("returns lines with the order", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    mockEmptyFulfillment(spies);

    const result = await run(db, { id: basePurchaseOrder.id });
    const { purchaseOrder } = expectOk<{
      purchaseOrder: { lines: unknown[] };
    }>(result as never);

    expect(purchaseOrder.lines).toHaveLength(1);
    expect(purchaseOrder.lines[0]).toMatchObject({ id: basePurchaseOrderLine.id });
  });

  it("returns revisionNumber on the order", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrderWithRevision);
    spies.select.mockReturnValueOnce([poLineForAmendment]);
    mockEmptyFulfillment(spies);

    const result = await run(db, { id: orderedPurchaseOrderWithRevision.id });
    const { purchaseOrder } = expectOk<{
      purchaseOrder: { revisionNumber: number };
    }>(result as never);

    expect(purchaseOrder.revisionNumber).toBe(1);
  });

  it("returns null when purchase order is not found", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, { id: "non-existent-id" });
    const { purchaseOrder } = expectOk<{ purchaseOrder: null }>(result as never);

    expect(purchaseOrder).toBeNull();
  });

  it("returns received and billed quantities per line", async () => {
    const postedReceiptLine = {
      ...baseGoodsReceiptLine,
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: basePurchaseOrderLine.id,
      quantity: "3",
    };
    const matchedBillLine = {
      ...basePurchaseBillLine,
      purchaseBillId: matchedPurchaseBill.id,
      purchaseOrderLineId: basePurchaseOrderLine.id,
      quantity: "2",
    };
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    spies.select.mockReturnValueOnce([postedReceiptLine]); // GoodsReceiptLine
    spies.select.mockReturnValueOnce([matchedBillLine]); // PurchaseBillLine
    spies.select.mockReturnValueOnce([matchedPurchaseBill]); // PurchaseBill (MATCHED)
    spies.select.mockReturnValueOnce([postedGoodsReceipt]); // GoodsReceipt (POSTED)

    const result = await run(db, { id: basePurchaseOrder.id });
    const { purchaseOrder } = expectOk<{
      purchaseOrder: {
        lines: {
          receivedQuantity: string;
          billedQuantity: string;
          remainingQuantityToReceive: string;
          remainingQuantityToBill: string;
        }[];
      };
    }>(result as never);

    expect(purchaseOrder.lines[0]).toMatchObject({
      receivedQuantity: "3",
      billedQuantity: "2",
      remainingQuantityToReceive: "7", // 10 - 3
      remainingQuantityToBill: "8", // 10 - 2
    });
  });
});
