import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CoNotDraftError,
  CoNotFoundError,
  ModifyItemChangedError,
  ModifyQuantityBelowBilledError,
  ModifyQuantityBelowReceivedError,
  ModifyUomChangedError,
  NoChangeOrderLinesError,
  RemoveLineHasBillsError,
  RemoveLineHasReceiptsError,
} from "../lib/errors.generated";
import {
  basePurchaseChangeOrder,
  baseChangeOrderLine,
  poLineForChangeOrder,
  submittedChangeOrder,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./submitPurchaseChangeOrder";

describe("submitPurchaseChangeOrder", () => {
  const addLine = {
    ...baseChangeOrderLine,
    id: "co-line-add",
    action: "ADD" as const,
    originalPurchaseOrderLineId: null,
    itemId: "item-1",
    quantity: "50",
    unitPrice: "200",
  };

  it("submits a valid draft change order with ADD lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder) // selectOne CO
      .mockReturnValueOnce([addLine]) // CO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // all PO lines for PO
      .mockReturnValueOnce([]) // GoodsReceipt POSTED
      .mockReturnValueOnce([]) // GoodsReceiptLine
      .mockReturnValueOnce([]) // PurchaseBill MATCHED
      .mockReturnValueOnce([]); // PurchaseBillLine
    const submitted = { ...basePurchaseChangeOrder, changeOrderStatus: "SUBMITTED" };
    spies.update.mockReturnValueOnce(submitted);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expect(expectOk(result).changeOrder.changeOrderStatus).toBe("SUBMITTED");
  });

  it("submits a valid draft change order with MODIFY lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const modifyLine = {
      ...baseChangeOrderLine,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "120",
      unitPrice: "100",
      itemId: null,
      unitId: null,
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder) // selectOne CO
      .mockReturnValueOnce([modifyLine]) // CO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // original PO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // all PO lines for PO
      .mockReturnValueOnce([]) // GoodsReceipt POSTED
      .mockReturnValueOnce([]) // GoodsReceiptLine
      .mockReturnValueOnce([]) // PurchaseBill MATCHED
      .mockReturnValueOnce([]); // PurchaseBillLine
    const submitted = { ...basePurchaseChangeOrder, changeOrderStatus: "SUBMITTED" };
    spies.update.mockReturnValueOnce(submitted);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expect(expectOk(result).changeOrder.changeOrderStatus).toBe("SUBMITTED");
  });

  it("submits a valid draft change order with REMOVE lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const removeLine = {
      ...baseChangeOrderLine,
      id: "co-line-remove",
      action: "REMOVE" as const,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: null,
      unitPrice: null,
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder) // selectOne CO
      .mockReturnValueOnce([removeLine]) // CO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // original PO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // all PO lines for PO
      .mockReturnValueOnce([]) // GoodsReceipt POSTED
      .mockReturnValueOnce([]) // GoodsReceiptLine
      .mockReturnValueOnce([]) // PurchaseBill MATCHED
      .mockReturnValueOnce([]); // PurchaseBillLine
    const submitted = { ...basePurchaseChangeOrder, changeOrderStatus: "SUBMITTED" };
    spies.update.mockReturnValueOnce(submitted);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expect(expectOk(result).changeOrder.changeOrderStatus).toBe("SUBMITTED");
  });

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

    const result = await run(db, { id: "missing-co" }, commandCtx);

    expectErr(result, CoNotFoundError);
  });

  it("returns error when change order is not in DRAFT status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(submittedChangeOrder);

    const result = await run(db, { id: submittedChangeOrder.id }, commandCtx);

    expectErr(result, CoNotDraftError);
  });

  it("returns error when change order has no lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder) // selectOne CO
      .mockReturnValueOnce([]); // no CO lines

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, NoChangeOrderLinesError);
  });

  it("returns error when MODIFY action changes the item", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const modifyLine = {
      ...baseChangeOrderLine,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      itemId: "different-item",
      unitId: null,
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([modifyLine])
      .mockReturnValueOnce([poLineForChangeOrder]) // original PO lines
      .mockReturnValueOnce([poLineForChangeOrder]) // all PO lines
      .mockReturnValueOnce([]) // GoodsReceipt POSTED
      .mockReturnValueOnce([]) // GoodsReceiptLine
      .mockReturnValueOnce([]) // PurchaseBill MATCHED
      .mockReturnValueOnce([]); // PurchaseBillLine

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, ModifyItemChangedError);
  });

  it("returns error when MODIFY action changes the unit of measure", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const modifyLine = {
      ...baseChangeOrderLine,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      itemId: null,
      unitId: "different-unit",
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([modifyLine])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, ModifyUomChangedError);
  });

  it("returns error when MODIFY action reduces quantity below received quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const modifyLine = {
      ...baseChangeOrderLine,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "3",
      itemId: null,
      unitId: null,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: "gr-1",
      purchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "5",
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([modifyLine])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([{ id: "gr-1", status: "POSTED" }]) // GoodsReceipt POSTED
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([]) // PurchaseBill MATCHED
      .mockReturnValueOnce([]);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, ModifyQuantityBelowReceivedError);
  });

  it("returns error when MODIFY action reduces quantity below billed quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const modifyLine = {
      ...baseChangeOrderLine,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "3",
      itemId: null,
      unitId: null,
    };
    const billLine = {
      id: "bill-line-1",
      purchaseBillId: "bill-1",
      purchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "5",
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([modifyLine])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([]) // GoodsReceipt POSTED
      .mockReturnValueOnce([]) // GoodsReceiptLine
      .mockReturnValueOnce([{ id: "bill-1", status: "MATCHED" }])
      .mockReturnValueOnce([billLine]);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, ModifyQuantityBelowBilledError);
  });

  it("returns error when REMOVE action targets a line with posted receipts", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const removeLine = {
      ...baseChangeOrderLine,
      id: "co-line-remove",
      action: "REMOVE" as const,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: null,
      unitPrice: null,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: "gr-1",
      purchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "2",
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([removeLine])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([{ id: "gr-1", status: "POSTED" }])
      .mockReturnValueOnce([grLine])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([]);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, RemoveLineHasReceiptsError);
  });

  it("returns error when REMOVE action targets a line with matched bills", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const removeLine = {
      ...baseChangeOrderLine,
      id: "co-line-remove",
      action: "REMOVE" as const,
      originalPurchaseOrderLineId: poLineForChangeOrder.id,
      quantity: null,
      unitPrice: null,
    };
    const billLine = {
      id: "bill-line-1",
      purchaseBillId: "bill-1",
      purchaseOrderLineId: poLineForChangeOrder.id,
      quantity: "2",
    };
    spies.select
      .mockReturnValueOnce(basePurchaseChangeOrder)
      .mockReturnValueOnce([removeLine])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([poLineForChangeOrder])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([])
      .mockReturnValueOnce([{ id: "bill-1", status: "MATCHED" }])
      .mockReturnValueOnce([billLine]);

    const result = await run(db, { id: basePurchaseChangeOrder.id }, commandCtx);

    expectErr(result, RemoveLineHasBillsError);
  });
});
