import { describe, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CloseReasonRequiredError,
  OpenQuantityRemainsError,
  PurchaseOrderNotClosableError,
  PurchaseOrderNotFoundError,
} from "../lib/errors.generated";
import { basePurchaseOrder, basePurchaseOrderLines } 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,
      status: "BILLED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 10, billedQuantity: 10 },
      ]),
    };
    spies.select.mockReturnValueOnce(billedOrder);
    spies.update.mockReturnValueOnce({ ...billedOrder, status: "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,
      status: "PARTIALLY_RECEIVED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 4, billedQuantity: 0 },
      ]),
    };
    spies.select.mockReturnValueOnce(partialOrder);
    spies.update.mockReturnValueOnce({
      ...partialOrder,
      status: "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,
      status: "PARTIALLY_RECEIVED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 4, billedQuantity: 0 },
      ]),
    };
    spies.select.mockReturnValueOnce(partialOrder);
    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,
      status: "PARTIALLY_RECEIVED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 4, billedQuantity: 0 },
      ]),
    };
    spies.select.mockReturnValueOnce(partialOrder);
    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,
      status: "RECEIVED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 10, billedQuantity: 4 },
      ]),
    };
    spies.select.mockReturnValueOnce(receivedOrder);
    spies.update.mockReturnValueOnce({ ...receivedOrder, status: "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,
      status: "PARTIALLY_RECEIVED",
      lines: JSON.stringify([
        { ...basePurchaseOrderLines[0], receivedQuantity: 4, billedQuantity: 0 },
      ]),
    };
    spies.select.mockReturnValueOnce(partialOrder);

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