import { describe, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CloseReasonRequiredError,
  OpenChangeOrderExistsError,
  OpenQuantityRemainsError,
  PurchaseOrderNotClosableError,
  PurchaseOrderNotFoundError,
} from "../lib/errors.generated";
import { basePurchaseOrder, basePurchaseOrderLine, postedGoodsReceipt } from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./closePurchaseOrder";

describe("closePurchaseOrder", () => {
  it("closes a fully billed purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const billedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "RECEIVED",
      billingStatus: "BILLED",
    };
    const fullyBilledLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: billedOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: fullyBilledLine.id,
      quantity: 10,
    };
    const billLine = {
      id: "bill-line-1",
      purchaseBillId: "matched-bill-1",
      purchaseOrderLineId: fullyBilledLine.id,
      quantity: 10,
    };
    spies.select
      .mockReturnValueOnce(billedOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([fullyBilledLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([{ id: "matched-bill-1", status: "MATCHED" }]) // PurchaseBill (status=MATCHED)
      .mockReturnValueOnce([billLine]); // PurchaseBillLine (by lineIds)
    spies.update.mockReturnValueOnce({ ...billedOrder, orderStatus: "CLOSED" });
    expectOk(await run(db, { id: billedOrder.id }, commandCtx));
  });

  it("closes a partially received order when remaining quantity is explicitly written off", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: partialLine.id,
      quantity: 4,
    };
    spies.select
      .mockReturnValueOnce(partialOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([partialLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([]) // PurchaseBill (status=MATCHED) — none
      .mockReturnValueOnce([]); // PurchaseBillLine (by lineIds) — none
    spies.update.mockReturnValueOnce({
      ...partialOrder,
      orderStatus: "CLOSED",
      closeReason: "Write off",
    });
    expectOk(
      await run(
        db,
        { id: partialOrder.id, writeOffRemaining: true, closeReason: "Write off" },
        commandCtx,
      ),
    );
  });

  it("returns error when purchase order does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);
    expectErr(await run(db, { id: "missing-order" }, commandCtx), PurchaseOrderNotFoundError);
  });

  it("returns error when purchase order status does not allow closure", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);
    expectErr(
      await run(db, { id: basePurchaseOrder.id }, commandCtx),
      PurchaseOrderNotClosableError,
    );
  });

  it("returns error when unresolved open quantity remains", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: partialLine.id,
      quantity: 4,
    };
    spies.select
      .mockReturnValueOnce(partialOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([partialLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([]) // PurchaseBill (status=MATCHED) — none
      .mockReturnValueOnce([]); // PurchaseBillLine (by lineIds) — none
    expectErr(await run(db, { id: partialOrder.id }, commandCtx), OpenQuantityRemainsError);
  });

  it("returns error when residual quantity exists without a close reason", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: partialLine.id,
      quantity: 4,
    };
    spies.select
      .mockReturnValueOnce(partialOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([partialLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([]) // PurchaseBill (status=MATCHED) — none
      .mockReturnValueOnce([]); // PurchaseBillLine (by lineIds) — none
    expectErr(
      await run(db, { id: partialOrder.id, writeOffRemaining: true }, commandCtx),
      CloseReasonRequiredError,
    );
  });

  it("allows closure without write-off when each line is fully received", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const receivedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "RECEIVED",
      billingStatus: "PARTIALLY_BILLED",
    };
    const fullyReceivedLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: receivedOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: fullyReceivedLine.id,
      quantity: 10,
    };
    spies.select
      .mockReturnValueOnce(receivedOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([fullyReceivedLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([]) // PurchaseBill (status=MATCHED) — none
      .mockReturnValueOnce([]); // PurchaseBillLine (by lineIds) — none
    spies.update.mockReturnValueOnce({ ...receivedOrder, orderStatus: "CLOSED" });

    expectOk(await run(db, { id: receivedOrder.id }, commandCtx));
  });

  it("returns OPEN_QUANTITY_REMAINS before requiring a close reason when write-off is not requested", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const partialOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
      receiptStatus: "PARTIALLY_RECEIVED",
      billingStatus: "NOT_BILLED",
    };
    const partialLine = {
      ...basePurchaseOrderLine,
      purchaseOrderId: partialOrder.id,
    };
    const grLine = {
      id: "gr-line-1",
      goodsReceiptId: postedGoodsReceipt.id,
      purchaseOrderLineId: partialLine.id,
      quantity: 4,
    };
    spies.select
      .mockReturnValueOnce(partialOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined) // selectFrom PurchaseChangeOrder (executeTakeFirst — no open CO)
      .mockReturnValueOnce([partialLine]) // selectFrom PurchaseOrderLine (execute → array)
      .mockReturnValueOnce([postedGoodsReceipt]) // GoodsReceipt (status=POSTED)
      .mockReturnValueOnce([grLine]) // GoodsReceiptLine (by lineIds)
      .mockReturnValueOnce([]) // PurchaseBill (status=MATCHED) — none
      .mockReturnValueOnce([]); // PurchaseBillLine (by lineIds) — none

    expectErr(
      await run(db, { id: partialOrder.id, closeReason: "Attempted close" }, commandCtx),
      OpenQuantityRemainsError,
    );
  });

  it("returns error when an open change order exists for the purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const orderedOrder = {
      ...basePurchaseOrder,
      orderStatus: "ORDERED",
    };
    const openCO = {
      id: "co-1",
      purchaseOrderId: orderedOrder.id,
      changeOrderStatus: "SUBMITTED",
    };
    spies.select
      .mockReturnValueOnce(orderedOrder) // selectOne PurchaseOrder
      .mockReturnValueOnce(openCO); // selectFrom PurchaseChangeOrder (executeTakeFirst — found)

    expectErr(await run(db, { id: orderedOrder.id }, commandCtx), OpenChangeOrderExistsError);
  });
});
