import { describe, expect, it, vi } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  GoodsReceiptNotDraftError,
  GoodsReceiptNotFoundError,
  InactiveExecutionReferenceError,
  StockMovementCreationFailedError,
  NoOpenReceiptQuantityError,
  OverReceiptToleranceExceededError,
  PurchaseOrderNotReceivableError,
} from "../lib/errors.generated";
import {
  commandCtx,
  expectErr,
  expectOk,
  mockBPQueries,
  mockInventoryCommands,
  mockItemQueries,
} from "../testing/commandTestUtils";
import {
  baseGoodsReceipt,
  baseGoodsReceiptLine,
  baseItem,
  basePurchaseOrderLine,
  orderedPurchaseOrder,
  postedGoodsReceipt,
} from "../testing/fixtures";
import { run } from "./postGoodsReceipt";

const bpQ = mockBPQueries({ partner: { id: baseGoodsReceipt.supplierId, status: "ACTIVE" } });
const itemQ = mockItemQueries({ item: baseItem });
const invC = mockInventoryCommands();

describe("postGoodsReceipt", () => {
  it("posts a valid goods receipt and increases purchase-order received quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // 1. GoodsReceipt (forUpdate)
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. businessPartnerQueries.getPartner (external call)
    // 3. GoodsReceiptLine (for receipt)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 4. PurchaseOrderLine (by poLineIds)
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 5. PurchaseOrder (by poIds, forUpdate)
    spies.select.mockReturnValueOnce([orderedPurchaseOrder]);
    // 6. GoodsReceipt (status=POSTED)
    spies.select.mockReturnValueOnce([]);
    // 7. GoodsReceiptLine (by poLineIds)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. PurchaseOrderLine (all for PO) for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. GoodsReceiptLine (for PO lines) for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("posts PO-unlinked receipt lines without affecting any purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const unlinkedLine = { ...baseGoodsReceiptLine, purchaseOrderLineId: null };
    // 1. GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. businessPartnerQueries.getPartner (external call)
    // 3. GoodsReceiptLine
    spies.select.mockReturnValueOnce([unlinkedLine]);
    // No PO line/order lookups needed (poLineIds is empty)
    // 4. GoodsReceipt (status=POSTED) -> always runs
    spies.select.mockReturnValueOnce([]);
    // 5. updateTable GoodsReceipt
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
    // No PO update calls should have happened — only the GoodsReceipt status update
    expect(spies.set).toHaveBeenCalledTimes(1);
  });

  it("posts a consolidated receipt spanning multiple purchase orders and updates each PO independently", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const secondPO = { ...orderedPurchaseOrder, id: "purchase-order-4" };
    const secondPOLine = {
      ...basePurchaseOrderLine,
      id: "po-line-2",
      purchaseOrderId: secondPO.id,
    };
    const secondReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-2",
      purchaseOrderLineId: secondPOLine.id,
    };

    // 1. GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. businessPartnerQueries.getPartner (external call)
    // 3. GoodsReceiptLine (two lines)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]);
    // 4. PurchaseOrderLine (by ids)
    spies.select.mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]);
    // 5. PurchaseOrder (by ids)
    spies.select.mockReturnValueOnce([orderedPurchaseOrder, secondPO]);
    // 6. GoodsReceipt (status=POSTED)
    spies.select.mockReturnValueOnce([]);
    // 7. GoodsReceiptLine (by poLineIds)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. PurchaseOrderLine (all for first PO) for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. GoodsReceiptLine (for first PO lines) for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder (first PO)
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });
    // 12. PurchaseOrderLine (all for second PO) for receiptStatus rollup
    spies.select.mockReturnValueOnce([secondPOLine]);
    // 13. GoodsReceiptLine (for second PO lines) for receiptStatus rollup
    spies.select.mockReturnValueOnce([secondReceiptLine]);
    // 14. updateTable PurchaseOrder (second PO)
    spies.update.mockReturnValueOnce({ ...secondPO, receiptStatus: "PARTIALLY_RECEIVED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("transitions multiple purchase orders to different receipt statuses from a single receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // First PO: partial receipt (4 of 10)
    // Second PO: full receipt (10 of 10)
    const secondPO = { ...orderedPurchaseOrder, id: "purchase-order-4" };
    const secondPOLine = {
      ...basePurchaseOrderLine,
      id: "po-line-2",
      purchaseOrderId: secondPO.id,
      quantity: "10",
    };
    const secondReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-2",
      purchaseOrderLineId: secondPOLine.id,
      quantity: "10",
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]) // 4. PurchaseOrderLine (by ids)
      .mockReturnValueOnce([orderedPurchaseOrder, secondPO]) // 5. PurchaseOrder (by ids)
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine]) // 9. PO lines rollup for first PO
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 10. GoodsReceiptLine rollup for first PO
      .mockReturnValueOnce([secondPOLine]) // PO lines rollup for second PO
      .mockReturnValueOnce([secondReceiptLine]); // GoodsReceiptLine rollup for second PO
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" }) // 8. GoodsReceipt
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" }) // First PO
      .mockReturnValueOnce({ ...secondPO, receiptStatus: "RECEIVED" }); // Second PO

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("transitions purchase order to partially received when open quantity remains", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine]) // 9. rollup PO lines
      .mockReturnValueOnce([baseGoodsReceiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({ receiptStatus: "PARTIALLY_RECEIVED" }),
    );
  });

  it("transitions purchase order to received when all quantity is posted", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const receiptLine = { ...baseGoodsReceiptLine, quantity: "10" };
    const orderLine = { ...basePurchaseOrderLine, quantity: "10" };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([receiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([orderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([receiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([orderLine]) // 9. rollup PO lines
      .mockReturnValueOnce([receiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ receiptStatus: "RECEIVED" }));
  });

  it("calls receiveStock for stock-item receipt lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // Only receiveStock (no receiveStockWithInspection) — standard receipt path
    const { receiveStock } = mockInventoryCommands();
    const receiveSpy = vi.fn(receiveStock);
    const mockInv = { receiveStock: receiveSpy };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine]) // 9. rollup PO lines
      .mockReturnValueOnce([baseGoodsReceiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(receiveSpy).toHaveBeenCalledWith(
      db,
      expect.objectContaining({
        itemId: baseItem.id,
        storageLocationId: baseGoodsReceiptLine.destinationStorageLocation,
        quantity: baseGoodsReceiptLine.quantity,
        unitCost: basePurchaseOrderLine.unitPrice,
        sourceId: baseGoodsReceipt.id,
      }),
      commandCtx,
    );
  });

  it("routes through receiveStockWithInspection when provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const baseMock = mockInventoryCommands();
    const inspectionSpy = vi.fn(baseMock.receiveStock);
    const defaultSpy = vi.fn(baseMock.receiveStock);
    const mockInv = {
      receiveStock: defaultSpy,
      receiveStockWithInspection: inspectionSpy,
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt)
      .mockReturnValueOnce([baseGoodsReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([orderedPurchaseOrder])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([baseGoodsReceiptLine])
      .mockReturnValueOnce([basePurchaseOrderLine])
      .mockReturnValueOnce([baseGoodsReceiptLine]);
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    // Inspection command should be called (not the default receiveStock)
    expect(inspectionSpy).toHaveBeenCalledWith(
      db,
      expect.objectContaining({
        itemId: baseItem.id,
        storageLocationId: baseGoodsReceiptLine.destinationStorageLocation,
        quantity: baseGoodsReceiptLine.quantity,
        unitCost: basePurchaseOrderLine.unitPrice,
        sourceId: baseGoodsReceipt.id,
      }),
      commandCtx,
    );
    // Default receiveStock must NOT be called when inspection command is provided
    expect(defaultSpy).not.toHaveBeenCalled();
  });

  it("skips receiveStock for a service receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const mockInv = mockInventoryCommands();
    const receiveSpy = (mockInv.receiveStock = vi.fn(mockInv.receiveStock));

    const serviceReceiptLine = { ...baseGoodsReceiptLine, requiresInventoryHandoff: false };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([serviceReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([serviceReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine]) // 9. rollup PO lines
      .mockReturnValueOnce([serviceReceiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(receiveSpy).not.toHaveBeenCalled();
  });

  it("returns error when goods receipt does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);
    expectErr(
      await run(db, { id: "missing-receipt" }, commandCtx, bpQ, itemQ, invC),
      GoodsReceiptNotFoundError,
    );
  });

  it("returns error when goods receipt is not draft", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(postedGoodsReceipt);
    expectErr(
      await run(db, { id: postedGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      GoodsReceiptNotDraftError,
    );
  });

  it("returns error when posting exceeds tolerance", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const receiptLine = { ...baseGoodsReceiptLine, quantity: "11" };
    const orderLine = { ...basePurchaseOrderLine, quantity: "10" };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([receiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([orderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([receiptLine]); // 7. GoodsReceiptLine (by poLineIds) — receiptLine from DRAFT GR won't be in postedGRIds, so alreadyReceived=0; openQty=10; 11 > 10
    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      OverReceiptToleranceExceededError,
    );
  });

  it("returns error when posting a receipt line with no open quantity remaining", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const orderLine = { ...basePurchaseOrderLine, quantity: "10" };
    const receiptLine = { ...baseGoodsReceiptLine, quantity: "1" };
    // Existing posted receipt lines that sum to 10 (fully received)
    const existingReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-existing",
      goodsReceiptId: postedGoodsReceipt.id,
      quantity: "10",
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([receiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([orderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([postedGoodsReceipt]) // 6. GoodsReceipt (status=POSTED) — one posted GR
      .mockReturnValueOnce([existingReceiptLine, receiptLine]); // 7. GoodsReceiptLine (by poLineIds) — existingReceiptLine is from posted GR, so alreadyReceived=10; openQty=0

    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      NoOpenReceiptQuantityError,
    );
  });

  it("returns error when any referenced purchase order is no longer receivable", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([{ ...orderedPurchaseOrder, orderStatus: "CANCELLED" }]); // 5. PurchaseOrder
    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      PurchaseOrderNotReceivableError,
    );
  });

  it("returns error when supplier is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    expectErr(
      await run(
        db,
        { id: baseGoodsReceipt.id },
        commandCtx,
        mockBPQueries({ partner: { id: baseGoodsReceipt.supplierId, status: "INACTIVE" } }),
        itemQ,
        invC,
      ),
      InactiveExecutionReferenceError,
    );
  });

  it("posts successfully when item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const inactiveItemQ = mockItemQueries({ item: { ...baseItem, status: "INACTIVE" } });
    // 1. GoodsReceipt
    spies.select.mockReturnValueOnce(baseGoodsReceipt);
    // 2. businessPartnerQueries.getPartner (external call)
    // 3. GoodsReceiptLine
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 4. PurchaseOrderLine
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 5. PurchaseOrder
    spies.select.mockReturnValueOnce([orderedPurchaseOrder]);
    // 6. GoodsReceipt (status=POSTED)
    spies.select.mockReturnValueOnce([]);
    // 7. GoodsReceiptLine (by poLineIds)
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 8. updateTable GoodsReceipt (set status=POSTED)
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" });
    // 9. PurchaseOrderLine (all for PO) for receiptStatus rollup
    spies.select.mockReturnValueOnce([basePurchaseOrderLine]);
    // 10. GoodsReceiptLine (for PO lines) for receiptStatus rollup
    spies.select.mockReturnValueOnce([baseGoodsReceiptLine]);
    // 11. updateTable PurchaseOrder
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, inactiveItemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("validates over-receipt against the PO line's quantity using received quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // PO line with quantity 10, existing posted receipt of 4, new receipt of 4 — should succeed
    const orderLine = {
      ...basePurchaseOrderLine,
      id: "po-line-revised",
      quantity: "10",
    };
    const newReceiptLine = {
      ...baseGoodsReceiptLine,
      purchaseOrderLineId: "po-line-revised",
      quantity: "4",
    };
    const existingReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-existing",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: "po-line-revised",
      quantity: "4",
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([newReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([orderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([postedGoodsReceipt]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([existingReceiptLine, newReceiptLine]); // 7. GoodsReceiptLine (by poLineIds)
    // Post succeeds: received = 4 (from posted GR); openQty = 10 - 4 = 6; 4 <= 6
    spies.update.mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" }); // 8. GoodsReceipt
    // Rollup queries
    spies.select
      .mockReturnValueOnce([orderLine]) // 9. PurchaseOrderLine (all for PO)
      .mockReturnValueOnce([existingReceiptLine, newReceiptLine]); // 10. GoodsReceiptLine (rollup)
    spies.update.mockReturnValueOnce({
      ...orderedPurchaseOrder,
      receiptStatus: "PARTIALLY_RECEIVED",
    });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
  });

  it("returns error when receipt line references a PO line not in the fetched orderLineMap", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // Receipt line references po-line-1 but PurchaseOrderLine query returns a different line
    const differentOrderLine = {
      ...basePurchaseOrderLine,
      id: "po-line-other",
      purchaseOrderId: orderedPurchaseOrder.id,
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine (references po-line-1)
      .mockReturnValueOnce([differentOrderLine]) // 4. PurchaseOrderLine (by ids) — doesn't include po-line-1
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([]); // 7. GoodsReceiptLine (by poLineIds)

    expectErr(
      await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, invC),
      NoOpenReceiptQuantityError,
    );
  });

  it("returns error when receiveStock fails", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const failingInvC = {
      receiveStock: vi.fn(() =>
        Promise.resolve({
          ok: false as const,
          error: new Error("inventory error"),
        }),
      ),
    };
    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine]); // 7. GoodsReceiptLine (by poLineIds)

    expectErr(
      await run(
        db,
        { id: baseGoodsReceipt.id },
        commandCtx,
        bpQ,
        itemQ,
        failingInvC as unknown as Parameters<typeof run>[5],
      ),
      StockMovementCreationFailedError,
    );
  });

  it("calls receiveStock once per stock-item receipt line and posts successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const mockInv = {
      receiveStock: vi.fn(mockInventoryCommands().receiveStock),
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine]) // 9. rollup PO lines
      .mockReturnValueOnce([baseGoodsReceiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
    expect(mockInv.receiveStock).toHaveBeenCalledTimes(1);
    expect(mockInv.receiveStock).toHaveBeenCalledWith(
      db,
      expect.objectContaining({
        itemId: baseItem.id,
        storageLocationId: baseGoodsReceiptLine.destinationStorageLocation,
        quantity: baseGoodsReceiptLine.quantity,
        unitCost: basePurchaseOrderLine.unitPrice,
        sourceId: baseGoodsReceipt.id,
      }),
      commandCtx,
    );
  });

  it("calls receiveStock for multiple stock-item receipt lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const secondPOLine = {
      ...basePurchaseOrderLine,
      id: "po-line-2",
      purchaseOrderId: orderedPurchaseOrder.id,
    };
    const secondReceiptLine = {
      ...baseGoodsReceiptLine,
      id: "gr-line-2",
      purchaseOrderLineId: secondPOLine.id,
    };
    const mockInv = {
      receiveStock: vi.fn(mockInventoryCommands().receiveStock),
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]) // 7. GoodsReceiptLine (by poLineIds)
      .mockReturnValueOnce([basePurchaseOrderLine, secondPOLine]) // 9. rollup PO lines
      .mockReturnValueOnce([baseGoodsReceiptLine, secondReceiptLine]); // 10. rollup GoodsReceiptLine
    spies.update
      .mockReturnValueOnce({ ...baseGoodsReceipt, status: "POSTED" })
      .mockReturnValueOnce({ ...orderedPurchaseOrder, receiptStatus: "PARTIALLY_RECEIVED" });

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(expectOk(result).goodsReceipt.status).toBe("POSTED");
    expect(mockInv.receiveStock).toHaveBeenCalledTimes(2);
  });

  it("returns error when receipt line requires inventory handoff but has no destination location", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const mockInv = mockInventoryCommands();
    const receiptLineNoLocation = {
      ...baseGoodsReceiptLine,
      destinationStorageLocation: null,
    };

    spies.select
      .mockReturnValueOnce(baseGoodsReceipt) // 1. GoodsReceipt
      .mockReturnValueOnce([receiptLineNoLocation]) // 3. GoodsReceiptLine
      .mockReturnValueOnce([basePurchaseOrderLine]) // 4. PurchaseOrderLine
      .mockReturnValueOnce([orderedPurchaseOrder]) // 5. PurchaseOrder
      .mockReturnValueOnce([]) // 6. GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([receiptLineNoLocation]); // 7. GoodsReceiptLine (by poLineIds)

    const result = await run(db, { id: baseGoodsReceipt.id }, commandCtx, bpQ, itemQ, mockInv);

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(StockMovementCreationFailedError);
    }
  });
});
