import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { DB } from "../generated/kysely-tailordb";
import {
  baseGoodsReceiptLine,
  basePurchaseBillLine,
  basePurchaseOrder,
  basePurchaseOrderLine,
  matchedPurchaseBill,
  orderedPurchaseOrderWithRevision,
  poLineForChangeOrder,
  postedGoodsReceipt,
} from "../testing/fixtures";
import { expectOk } from "../testing/commandTestUtils";
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 currentRevisionNumber on the order", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrderWithRevision);
    spies.select.mockReturnValueOnce([poLineForChangeOrder]);
    mockEmptyFulfillment(spies);

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

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

  it("returns lineStatus, revisionNumber, rootLineId, and supersededByLineId on each line", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrderWithRevision);
    spies.select.mockReturnValueOnce([poLineForChangeOrder]);
    mockEmptyFulfillment(spies);

    const result = await run(db, { id: orderedPurchaseOrderWithRevision.id });
    const { purchaseOrder } = expectOk<{
      purchaseOrder: {
        lines: {
          lineStatus: string | null;
          revisionNumber: number | null;
          rootLineId: string | null;
          supersededByLineId: string | null;
        }[];
      };
    }>(result as never);

    expect(purchaseOrder.lines[0]).toMatchObject({
      lineStatus: "ACTIVE",
      revisionNumber: 1,
      rootLineId: null,
      supersededByLineId: null,
    });
  });

  it("returns SUPERSEDED lines alongside ACTIVE lines for audit", async () => {
    const supersededLine = {
      ...poLineForChangeOrder,
      id: "po-line-superseded",
      lineStatus: "SUPERSEDED" as const,
      supersededByLineId: poLineForChangeOrder.id,
    };
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrderWithRevision);
    spies.select.mockReturnValueOnce([poLineForChangeOrder, supersededLine]);
    mockEmptyFulfillment(spies);

    const result = await run(db, { id: orderedPurchaseOrderWithRevision.id });
    const { purchaseOrder } = expectOk<{
      purchaseOrder: { lines: { id: string; lineStatus: string | null }[] };
    }>(result as never);

    expect(purchaseOrder.lines).toHaveLength(2);
    const statuses = purchaseOrder.lines.map((l) => l.lineStatus);
    expect(statuses).toContain("ACTIVE");
    expect(statuses).toContain("SUPERSEDED");
  });

  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 lineage-aware 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
    });
  });

  it("aggregates received and billed quantities across lineage", async () => {
    const supersededLine = {
      ...basePurchaseOrderLine,
      id: "po-line-original",
      lineStatus: "SUPERSEDED" as const,
      supersededByLineId: "po-line-revised",
      rootLineId: null,
    };
    const activeLine = {
      ...basePurchaseOrderLine,
      id: "po-line-revised",
      lineStatus: "ACTIVE" as const,
      supersededByLineId: null,
      rootLineId: "po-line-original",
      quantity: "20",
    };
    // Receipt posted against the superseded line
    const receiptLine = {
      ...baseGoodsReceiptLine,
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: "po-line-original",
      quantity: "5",
    };
    // Bill matched against the active line
    const billLine = {
      ...basePurchaseBillLine,
      purchaseBillId: matchedPurchaseBill.id,
      purchaseOrderLineId: "po-line-revised",
      quantity: "3",
    };

    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    spies.select.mockReturnValueOnce([supersededLine, activeLine]);
    spies.select.mockReturnValueOnce([receiptLine]); // GoodsReceiptLine
    spies.select.mockReturnValueOnce([billLine]); // 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: {
          id: string;
          receivedQuantity: string;
          billedQuantity: string;
          remainingQuantityToReceive: string;
          remainingQuantityToBill: string;
        }[];
      };
    }>(result as never);

    // ACTIVE line (quantity=20) should see lineage-aware totals: received=5 (from superseded), billed=3
    const active = purchaseOrder.lines.find((l) => l.id === "po-line-revised")!;
    expect(active).toMatchObject({
      receivedQuantity: "5",
      billedQuantity: "3",
      remainingQuantityToReceive: "15", // 20 - 5
      remainingQuantityToBill: "17", // 20 - 3
    });

    // SUPERSEDED line (quantity=10) also sees same lineage totals
    const superseded = purchaseOrder.lines.find((l) => l.id === "po-line-original")!;
    expect(superseded).toMatchObject({
      receivedQuantity: "5",
      billedQuantity: "3",
      remainingQuantityToReceive: "5", // 10 - 5
      remainingQuantityToBill: "7", // 10 - 3
    });
  });
});
