import { describe, it, expect, vi } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PaymentTermInactiveError,
  PaymentTermNotFoundError,
  EmptyAmendmentChangesError,
  PoNotFoundError,
  PoNotOrderedError,
  LineNotFoundError,
  ModifyQuantityBelowReceivedError,
  ModifyQuantityBelowBilledError,
  RemoveLineHasReceiptsError,
  RemoveLineHasBillsError,
  AddInvalidQuantityError,
  ItemNotFoundError,
  ProtectedHeaderFieldError,
  ProtectedLineFieldError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  ReceivingSiteRequiredError,
  RemoveLineHasDownstreamReferencesError,
} from "../lib/errors.generated";
import {
  commandCtx,
  expectErr,
  mockInventoryCommands,
  mockInventoryQueries,
  mockItemQueries,
} from "../testing/commandTestUtils";
import {
  baseItem,
  basePaymentTerm,
  basePaymentTermLine,
  baseReceivingSite,
  baseUnit,
  inactivePaymentTerm,
  orderedPurchaseOrderWithRevision,
  poLineForAmendment,
} from "../testing/fixtures";
import { run as runCommand } from "./amendOrderedPurchaseOrder";

function run(
  db: Parameters<typeof runCommand>[0],
  input: Parameters<typeof runCommand>[1],
  ctx: Parameters<typeof runCommand>[2],
  itemManagementQueries: Parameters<typeof runCommand>[3],
  inventoryCommands: Parameters<typeof runCommand>[4] = mockInventoryCommands(),
  inventoryQueries: Parameters<typeof runCommand>[5] = mockInventoryQueries(),
) {
  return runCommand(db, input, ctx, itemManagementQueries, inventoryCommands, inventoryQueries);
}

const po = orderedPurchaseOrderWithRevision;
const poLine = poLineForAmendment;

// Test fixture: envelope row returned from the insert with returningAll().
const envelopeReturn = {
  id: "po-revision-1",
  purchaseOrderId: po.id,
  revisionNumber: 1,
  reason: null,
  amendedByUserId: commandCtx.actorId,
  createdAt: new Date(),
  updatedAt: null,
};

describe("amendOrderedPurchaseOrder", () => {
  it("amends a PO with a MODIFY action (line ID unchanged, values updated)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po) // PurchaseOrder (forUpdate)
      .mockReturnValueOnce([poLine]) // PurchaseOrderLine
      .mockReturnValueOnce([]) // Inventory receipt evidence (POSTED) — none
      .mockReturnValueOnce([]) // PurchaseBillLine joined PurchaseBill (MATCHED) — none
      .mockReturnValueOnce([]) // Recalc: Inventory receipt evidenceLine (POSTED)
      .mockReturnValueOnce([]); // Recalc: PurchaseBillLine (MATCHED)
    spies.update
      .mockReturnValueOnce(undefined) // PurchaseOrderLine update
      .mockReturnValueOnce(po); // PurchaseOrder update (returningAll)
    spies.insert
      .mockReturnValueOnce(envelopeReturn) // PurchaseOrderRevision insert (returningAll)
      .mockReturnValueOnce(undefined); // PurchaseOrderFieldChange batch insert

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Price change",
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8", unitPrice: "90" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("updates the unique inventory supply plan for a modified PO line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const inventoryCommands = mockInventoryCommands();
    const updateSupplySpy = vi.fn(inventoryCommands.updateInventorySupplyPlan);
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
      {
        ...inventoryCommands,
        updateInventorySupplyPlan: updateSupplySpy,
      },
      mockInventoryQueries({
        supplyPlans: [{ id: "supply-plan-1", sourceLineId: poLine.id }],
      }),
    );

    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
    expect(updateSupplySpy).toHaveBeenCalledWith(
      db,
      expect.objectContaining({
        id: "supply-plan-1",
        sourceType: "PURCHASE_ORDER",
        sourceId: po.id,
        sourceLineId: poLine.id,
        expectedQuantity: "8",
      }),
      commandCtx,
    );
  });

  it("zeros the existing supply plan when an active PO line no longer requires physical receipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const inventoryCommands = mockInventoryCommands();
    const updateSupplySpy = vi.fn(inventoryCommands.updateInventorySupplyPlan);
    const closeSupplySpy = vi.fn(inventoryCommands.closeInventorySupplyPlan);
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, requiresPhysicalReceipt: false }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
      {
        ...inventoryCommands,
        updateInventorySupplyPlan: updateSupplySpy,
        closeInventorySupplyPlan: closeSupplySpy,
      },
      mockInventoryQueries({
        supplyPlans: [{ id: "supply-plan-1", sourceLineId: poLine.id }],
      }),
    );

    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
    expect(updateSupplySpy).toHaveBeenCalledWith(
      db,
      { id: "supply-plan-1", expectedQuantity: "0" },
      commandCtx,
    );
    expect(closeSupplySpy).not.toHaveBeenCalled();
  });

  it("amends a PO with a REMOVE action (line physically deleted)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce(undefined) // Draft inventory receipt evidence check
      .mockReturnValueOnce(undefined) // Draft/blocked PurchaseBillLine check
      .mockReturnValueOnce([]) // Recalc receipt
      .mockReturnValueOnce([]); // Recalc bill
    spies.delete.mockReturnValueOnce(undefined); // PurchaseOrderLine delete
    spies.update.mockReturnValueOnce(po); // PurchaseOrder update
    spies.insert
      .mockReturnValueOnce(envelopeReturn) // envelope
      .mockReturnValueOnce(undefined); // field changes

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Line no longer needed",
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("amends a PO with an ADD action (new line created, no line receivingSiteId — header is used)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newLine = {
      id: "new-po-line-1",
      purchaseOrderId: po.id,
      itemId: baseItem.id,
      itemSnapshotName: baseItem.name,
      itemSnapshotSku: baseItem.sku,
      quantity: "5",
      unitPrice: "50",
      unitId: baseUnit.id,
      receivingSiteId: null,
      requiresPhysicalReceipt: true,
      createdAt: new Date(),
      updatedAt: null,
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([]) // Recalc receipt
      .mockReturnValueOnce([]); // Recalc bill
    spies.insert
      .mockReturnValueOnce([newLine]) // PurchaseOrderLine insert
      .mockReturnValueOnce(envelopeReturn) // envelope
      .mockReturnValueOnce(undefined); // field changes
    spies.update.mockReturnValueOnce(po);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
  });

  it("writes one PurchaseOrderRevision envelope per amendment", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        reason: "Adjust quantity",
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({
        purchaseOrderId: po.id,
        revisionNumber: 1,
        reason: "Adjust quantity",
        amendedByUserId: commandCtx.actorId,
      }),
    );
  });

  it("writes PurchaseOrderFieldChange rows for line MODIFY (only changed fields)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    // The field-change batch insert call: only `quantity` actually changed
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          revisionId: envelopeReturn.id,
          recordType: "LINE",
          recordId: poLine.id,
          fieldName: "quantity",
          changeKind: "MODIFIED",
          oldValue: poLine.quantity,
          newValue: "8",
        }),
      ]),
    );
  });

  it("writes PurchaseOrderFieldChange rows for line REMOVE (one per non-null snapshot field)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.delete.mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        reason: "Remove line",
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "LINE",
          recordId: poLine.id,
          fieldName: "quantity",
          changeKind: "REMOVED",
          oldValue: poLine.quantity,
          newValue: null,
        }),
        expect.objectContaining({
          fieldName: "unitPrice",
          changeKind: "REMOVED",
          newValue: null,
        }),
      ]),
    );
  });

  it("writes PurchaseOrderFieldChange rows for line ADD (one per non-null snapshot field)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newLine = {
      id: "new-po-line-1",
      purchaseOrderId: po.id,
      itemId: baseItem.id,
      itemSnapshotName: baseItem.name,
      itemSnapshotSku: baseItem.sku,
      quantity: "5",
      unitPrice: "50",
      unitId: baseUnit.id,
      receivingSiteId: null,
      requiresPhysicalReceipt: true,
      createdAt: new Date(),
      updatedAt: null,
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.insert
      .mockReturnValueOnce([newLine])
      .mockReturnValueOnce(envelopeReturn)
      .mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(po);

    await run(
      db,
      {
        id: po.id,
        reason: "Add new line",
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "LINE",
          recordId: newLine.id,
          fieldName: "quantity",
          changeKind: "ADDED",
          oldValue: null,
          newValue: "5",
        }),
        expect.objectContaining({
          fieldName: "unitPrice",
          changeKind: "ADDED",
          oldValue: null,
          newValue: "50",
        }),
      ]),
    );
  });

  it("amends a PO header field (e.g. extension field) and records a HEADER MODIFIED row", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poWithExt = {
      ...po,
      // Simulate an extension field on the live header
      ...({ exFactoryDate: "2026-05-01" } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(poWithExt)
      .mockReturnValueOnce([poLine])
      // No MODIFY/REMOVE → skip received/billed lookups
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(poWithExt);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Ex-factory delay",
        lineChanges: [],
        headerChanges: { exFactoryDate: "2026-05-15" },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "HEADER",
          recordId: po.id,
          fieldName: "exFactoryDate",
          changeKind: "MODIFIED",
          oldValue: "2026-05-01",
          newValue: "2026-05-15",
        }),
      ]),
    );
  });

  it("amends receivingSiteId as a validated header default", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newReceivingSite = { ...baseReceivingSite, id: "site-2" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([newReceivingSite])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce({
      ...po,
      receivingSiteId: newReceivingSite.id,
    });
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { receivingSiteId: newReceivingSite.id },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({ receivingSiteId: newReceivingSite.id }),
    );
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "HEADER",
          recordId: po.id,
          fieldName: "receivingSiteId",
          changeKind: "MODIFIED",
          oldValue: po.receivingSiteId,
          newValue: newReceivingSite.id,
        }),
      ]),
    );
  });

  it("amends paymentTermId and refreshes paymentTermSnapshotLines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newPaymentTerm = { ...basePaymentTerm, id: "payment-term-3" };
    const snapshotLines = [
      {
        kind: basePaymentTermLine.kind,
        days: basePaymentTermLine.days,
        fixedDayOfMonth: basePaymentTermLine.fixedDayOfMonth,
        percentage: basePaymentTermLine.percentage,
        fixedAmount: basePaymentTermLine.fixedAmount,
        note: basePaymentTermLine.note,
      },
    ];
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce(newPaymentTerm)
      .mockReturnValueOnce(snapshotLines)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce({
      ...po,
      paymentTermId: newPaymentTerm.id,
    });
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { paymentTermId: newPaymentTerm.id },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.set).toHaveBeenCalledWith(
      expect.objectContaining({
        paymentTermId: newPaymentTerm.id,
        paymentTermSnapshotLines: snapshotLines,
      }),
    );
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "HEADER",
          recordId: po.id,
          fieldName: "paymentTermId",
          changeKind: "MODIFIED",
          oldValue: po.paymentTermId,
          newValue: newPaymentTerm.id,
        }),
      ]),
    );
  });

  it("increments envelope revisionNumber from the existing maximum", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce({ revisionNumber: 3 }); // existing top revisionNumber
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({
        purchaseOrderId: po.id,
        revisionNumber: 4,
      }),
    );
  });

  it("recalculates receiptStatus after amendment", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const grLine = { purchaseOrderLineId: poLine.id, quantity: "4" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([]);
    spies.update
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce({ ...po, receiptStatus: "PARTIALLY_RECEIVED" });
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, unitPrice: "90" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
    expect(result.value.purchaseOrder.receiptStatus).toBe("PARTIALLY_RECEIVED");
  });

  it("recalculates billingStatus after amendment", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const billLine = { purchaseOrderLineId: poLine.id, quantity: "4" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([billLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([billLine]);
    spies.update
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce({ ...po, billingStatus: "PARTIALLY_BILLED" });
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, unitPrice: "90" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
    expect(result.value.purchaseOrder.billingStatus).toBe("PARTIALLY_BILLED");
  });

  it("supports price-only amendment (quantity not provided)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, unitPrice: "120" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;
    expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ quantity: poLine.quantity }));
  });

  it("returns error when both headerChanges and lineChanges are empty", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po);

    const result = await run(
      db,
      { id: po.id, lineChanges: [] },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, EmptyAmendmentChangesError);
  });

  it("returns error when the amendment contains no actual field changes", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      { id: po.id, lineChanges: [{ action: "MODIFY", lineId: poLine.id }] },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expectErr(result, EmptyAmendmentChangesError);
    expect(spies.update).not.toHaveBeenCalled();
    expect(spies.insert).not.toHaveBeenCalled();
  });

  it("returns error when a header change targets a protected field", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        // supplierId is a protected field; pass via runtime escape hatch to verify the runtime guard
        headerChanges: { supplierId: "new-supplier" } as never,
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ProtectedHeaderFieldError);
  });

  it("returns error when paymentTermId header change references a missing payment term", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { paymentTermId: "missing-payment-term" },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expectErr(result, PaymentTermNotFoundError);
    expect(spies.update).not.toHaveBeenCalled();
    expect(spies.insert).not.toHaveBeenCalled();
  });

  it("returns error when paymentTermId header change references an inactive payment term", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce(inactivePaymentTerm);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { paymentTermId: inactivePaymentTerm.id },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expectErr(result, PaymentTermInactiveError);
    expect(spies.update).not.toHaveBeenCalled();
    expect(spies.insert).not.toHaveBeenCalled();
  });

  it("returns error when PO does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      { id: "missing-po", lineChanges: [] },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, PoNotFoundError);
  });

  it("returns error when PO is not ORDERED", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...po, orderStatus: "DRAFT" });

    const result = await run(
      db,
      { id: po.id, lineChanges: [] },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, PoNotOrderedError);
  });

  it("returns error when MODIFY references a non-existent line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: "missing-line", quantity: "5" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, LineNotFoundError);
  });

  it("returns error when MODIFY reduces quantity below received", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const grLine = { purchaseOrderLineId: poLine.id, quantity: "8" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "5" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ModifyQuantityBelowReceivedError);
  });

  it("returns error when MODIFY reduces quantity below billed", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const billLine = { purchaseOrderLineId: poLine.id, quantity: "8" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([billLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "5" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ModifyQuantityBelowBilledError);
  });

  it("returns error when MODIFY reduces quantity below posted + draft received", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const grLines = [
      { purchaseOrderLineId: poLine.id, quantity: "3" },
      { purchaseOrderLineId: poLine.id, quantity: "5" },
    ];
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce(grLines)
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "6" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ModifyQuantityBelowReceivedError);
  });

  it("returns error when MODIFY reduces quantity below matched + draft/blocked billed", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const billLines = [
      { purchaseOrderLineId: poLine.id, quantity: "2" },
      { purchaseOrderLineId: poLine.id, quantity: "5" },
    ];
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce(billLines);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "6" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ModifyQuantityBelowBilledError);
  });

  it("returns error when REMOVE targets a line with posted receipts", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const grLine = { purchaseOrderLineId: poLine.id, quantity: "4" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, RemoveLineHasReceiptsError);
  });

  it("returns error when REMOVE targets a line with matched bills", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const billLine = { purchaseOrderLineId: poLine.id, quantity: "4" };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([billLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, RemoveLineHasBillsError);
  });

  it("returns error when REMOVE targets a line referenced by draft downstream documents", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce({ id: "draft-gr-line-1" });

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect((result.error as Error).name).toBe("RemoveLineHasDownstreamReferencesError");
    }
  });

  it("returns error when ADD has zero or negative quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce([poLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "0",
            unitPrice: "50",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, AddInvalidQuantityError);
  });

  it("returns error when ADD references a non-existent item", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce([poLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [
          {
            action: "ADD",
            itemId: "non-existent-item",
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: null }),
    );
    expectErr(result, ItemNotFoundError);
  });

  it("amends a PO line with extension fields via MODIFY and records LINE MODIFIED rows for changed extension fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineWithExt = {
      ...poLine,
      ...({ note: "old note", priority: "LOW" } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLineWithExt])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Update note",
        lineChanges: [
          { action: "MODIFY", lineId: poLine.id, note: "new note", priority: "LOW" } as never,
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ note: "new note" }));
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "LINE",
          recordId: poLine.id,
          fieldName: "note",
          changeKind: "MODIFIED",
          oldValue: "old note",
          newValue: "new note",
        }),
      ]),
    );
    // priority did not change → no MODIFIED row for it
    expect(spies.values).not.toHaveBeenCalledWith(
      expect.arrayContaining([expect.objectContaining({ fieldName: "priority" })]),
    );
  });

  it("amends a PO line with extension fields via ADD and records LINE ADDED rows for non-null extension fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newLine = {
      id: "new-po-line-1",
      purchaseOrderId: po.id,
      itemId: baseItem.id,
      itemSnapshotName: baseItem.name,
      itemSnapshotSku: baseItem.sku,
      quantity: "5",
      unitPrice: "50",
      unitId: baseUnit.id,
      receivingSiteId: null,
      requiresPhysicalReceipt: true,
      note: "added note",
      priority: "HIGH",
      createdAt: new Date(),
      updatedAt: null,
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.insert
      .mockReturnValueOnce([newLine])
      .mockReturnValueOnce(envelopeReturn)
      .mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(po);

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Add line with notes",
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
            note: "added note",
            priority: "HIGH",
          } as never,
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([expect.objectContaining({ note: "added note", priority: "HIGH" })]),
    );
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "LINE",
          recordId: newLine.id,
          fieldName: "note",
          changeKind: "ADDED",
          oldValue: null,
          newValue: "added note",
        }),
        expect.objectContaining({
          fieldName: "priority",
          changeKind: "ADDED",
          newValue: "HIGH",
        }),
      ]),
    );
  });

  it("returns error when a line MODIFY change targets a protected line field", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        // unitId is a protected line field; pass via runtime escape hatch
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, unitId: "unit-2" } as never],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ProtectedLineFieldError);
  });

  it("returns error when a line ADD change targets a protected line field", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce([poLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
            // itemSnapshotName is a protected line field; pass via runtime escape hatch
            itemSnapshotName: "spoofed",
          } as never,
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ProtectedLineFieldError);
  });

  it("writes PurchaseOrderFieldChange rows for line REMOVE including non-null extension fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineWithExt = {
      ...poLine,
      ...({ note: "removed note", priority: "HIGH", obsolete: null } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLineWithExt])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.delete.mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        reason: "Remove line with extensions",
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          fieldName: "note",
          changeKind: "REMOVED",
          oldValue: "removed note",
          newValue: null,
        }),
        expect.objectContaining({
          fieldName: "priority",
          changeKind: "REMOVED",
          oldValue: "HIGH",
          newValue: null,
        }),
      ]),
    );
    // null-valued extension fields should not appear
    expect(spies.values).not.toHaveBeenCalledWith(
      expect.arrayContaining([expect.objectContaining({ fieldName: "obsolete" })]),
    );
  });

  it("amends a PO line with explicit clear of an extension field via MODIFY (null value)", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineWithExt = {
      ...poLine,
      ...({ note: "old note" } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLineWithExt])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        reason: "Clear note",
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, note: null } as never],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ note: null }));
    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          fieldName: "note",
          changeKind: "MODIFIED",
          oldValue: "old note",
          newValue: null,
        }),
      ]),
    );
  });

  it("skips a MODIFIED row for a line MODIFY whose extension field value is unchanged", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineWithExt = {
      ...poLine,
      ...({ note: "same" } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLineWithExt])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        // quantity changes (so the amendment is not empty), note is provided but unchanged
        lineChanges: [
          { action: "MODIFY", lineId: poLine.id, quantity: "8", note: "same" } as never,
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).not.toHaveBeenCalledWith(
      expect.arrayContaining([expect.objectContaining({ fieldName: "note" })]),
    );
  });

  it("supports combining header, MODIFY, ADD, and REMOVE in a single amendment", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const otherPoLine = { ...poLine, id: "po-line-other" };
    const newLine = {
      id: "new-po-line-2",
      purchaseOrderId: po.id,
      itemId: baseItem.id,
      itemSnapshotName: baseItem.name,
      itemSnapshotSku: baseItem.sku,
      quantity: "3",
      unitPrice: "30",
      unitId: baseUnit.id,
      receivingSiteId: null,
      requiresPhysicalReceipt: true,
      createdAt: new Date(),
      updatedAt: null,
    };
    spies.select
      .mockReturnValueOnce(po) // PurchaseOrder forUpdate
      .mockReturnValueOnce([poLine, otherPoLine]) // PurchaseOrderLines
      .mockReturnValueOnce([]) // received lookup
      .mockReturnValueOnce([]) // billed lookup
      .mockReturnValueOnce(undefined) // draft GR for REMOVE
      .mockReturnValueOnce(undefined) // draft/blocked bill for REMOVE
      .mockReturnValueOnce([]) // recalc receipt
      .mockReturnValueOnce([]); // recalc bill
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.delete.mockReturnValueOnce(undefined);
    spies.insert
      .mockReturnValueOnce([newLine]) // ADD insert
      .mockReturnValueOnce(envelopeReturn) // envelope
      .mockReturnValueOnce(undefined); // field changes

    const result = await run(
      db,
      {
        id: po.id,
        reason: "Bundled amendment",
        headerChanges: { externalSupplierOrderReference: "PO-EXT-NEW" },
        lineChanges: [
          { action: "MODIFY", lineId: poLine.id, unitPrice: "120" },
          { action: "REMOVE", lineId: otherPoLine.id },
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "3",
            unitPrice: "30",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "HEADER",
          fieldName: "externalSupplierOrderReference",
          changeKind: "MODIFIED",
          newValue: "PO-EXT-NEW",
        }),
        expect.objectContaining({
          recordType: "LINE",
          recordId: poLine.id,
          fieldName: "unitPrice",
          changeKind: "MODIFIED",
          newValue: "120",
        }),
        expect.objectContaining({
          recordType: "LINE",
          recordId: otherPoLine.id,
          changeKind: "REMOVED",
        }),
        expect.objectContaining({
          recordType: "LINE",
          recordId: newLine.id,
          changeKind: "ADDED",
        }),
      ]),
    );
  });

  it("returns error when MODIFY has zero or negative quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "0" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, InvalidQuantityError);
  });

  it("returns error when MODIFY has negative unitPrice", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, unitPrice: "-1" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, InvalidUnitPriceError);
  });

  it("returns error when MODIFY would leave a physical-receipt line without a receiving site", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poNoSite = { ...po, receivingSiteId: null };
    const poLineNoSite = { ...poLine, receivingSiteId: null, requiresPhysicalReceipt: true };
    spies.select
      .mockReturnValueOnce(poNoSite)
      .mockReturnValueOnce([poLineNoSite])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, quantity: "8" }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ReceivingSiteRequiredError);
  });

  it("returns error when ADD would create a physical-receipt line without a receiving site", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poNoSite = { ...po, receivingSiteId: null };
    spies.select.mockReturnValueOnce(poNoSite).mockReturnValueOnce([poLine]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [
          {
            action: "ADD",
            itemId: baseItem.id,
            quantity: "5",
            unitPrice: "50",
            unitId: baseUnit.id,
          },
        ],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ReceivingSiteRequiredError);
  });

  it("returns error when clearing header receivingSiteId leaves a remaining physical-receipt line without one", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineNoSite = { ...poLine, receivingSiteId: null, requiresPhysicalReceipt: true };
    spies.select.mockReturnValueOnce(po).mockReturnValueOnce([poLineNoSite]);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { receivingSiteId: null },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, ReceivingSiteRequiredError);
  });

  it("returns error when REMOVE targets a line referenced by draft or blocked bills", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce(undefined) // draft GR check — none
      .mockReturnValueOnce({ purchaseOrderLineId: poLine.id }); // draft/blocked bill — found

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "REMOVE", lineId: poLine.id }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expectErr(result, RemoveLineHasDownstreamReferencesError);
  });

  it("writes ISO date strings in audit rows when header orderDate changes", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const oldDate = po.orderDate;
    const newDate = new Date("2026-06-01T00:00:00.000Z");
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce({ ...po, orderDate: newDate });
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        id: po.id,
        lineChanges: [],
        headerChanges: { orderDate: newDate },
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );
    expect(result.ok).toBe(true);
    if (!result.ok) throw result.error;

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          recordType: "HEADER",
          fieldName: "orderDate",
          changeKind: "MODIFIED",
          oldValue: oldDate.toISOString(),
          newValue: newDate.toISOString(),
        }),
      ]),
    );
  });

  it("writes boolean strings in audit rows when line MODIFY toggles requiresPhysicalReceipt", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, requiresPhysicalReceipt: false }],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          fieldName: "requiresPhysicalReceipt",
          changeKind: "MODIFIED",
          oldValue: "true",
          newValue: "false",
        }),
      ]),
    );
  });

  it("serializes numeric extension field values in line MODIFY audit rows", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poLineWithRank = {
      ...poLine,
      ...({ priorityRank: 5 } as Record<string, unknown>),
    };
    spies.select
      .mockReturnValueOnce(po)
      .mockReturnValueOnce([poLineWithRank])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);
    spies.update.mockReturnValueOnce(undefined).mockReturnValueOnce(po);
    spies.insert.mockReturnValueOnce(envelopeReturn).mockReturnValueOnce(undefined);

    await run(
      db,
      {
        id: po.id,
        lineChanges: [{ action: "MODIFY", lineId: poLine.id, priorityRank: 10 } as never],
      },
      commandCtx,
      mockItemQueries({ item: baseItem }),
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.arrayContaining([
        expect.objectContaining({
          fieldName: "priorityRank",
          changeKind: "MODIFIED",
          oldValue: "5",
          newValue: "10",
        }),
      ]),
    );
  });
});
