import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  OpenChangeOrderExistsError,
  PoNotFoundError,
  PoNotOrderedError,
} from "../lib/errors.generated";
import {
  basePurchaseChangeOrder,
  basePurchaseOrder,
  orderedPurchaseOrderWithRevision,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createPurchaseChangeOrder";

describe("createPurchaseChangeOrder", () => {
  it("creates a draft change order against a valid ordered purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(orderedPurchaseOrderWithRevision) // selectOne PurchaseOrder
      .mockReturnValueOnce(undefined); // no open CO
    spies.insert.mockReturnValueOnce({
      ...basePurchaseChangeOrder,
      purchaseOrderId: orderedPurchaseOrderWithRevision.id,
    }); // insert CO
    // insert CO lines
    spies.insert.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        purchaseOrderId: orderedPurchaseOrderWithRevision.id,
        reason: "Price renegotiation",
        lines: [
          {
            action: "MODIFY",
            originalPurchaseOrderLineId: "po-line-10",
            quantity: "120",
            unitPrice: "100",
          },
        ],
      },
      commandCtx,
    );

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

  it("auto-sets fromRevisionNumber from the PO's currentRevisionNumber", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const poWithRevision3 = {
      ...orderedPurchaseOrderWithRevision,
      currentRevisionNumber: 3,
    };
    spies.select.mockReturnValueOnce(poWithRevision3).mockReturnValueOnce(undefined); // no open CO
    spies.insert.mockReturnValueOnce({
      ...basePurchaseChangeOrder,
      fromRevisionNumber: 3,
    });

    const result = await run(
      db,
      {
        purchaseOrderId: poWithRevision3.id,
        reason: "Update",
        lines: [],
      },
      commandCtx,
    );

    expectOk(result);
    expect(spies.values).toHaveBeenCalledWith(expect.objectContaining({ fromRevisionNumber: 3 }));
  });

  it("creates change order lines with ADD, MODIFY, and REMOVE actions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(orderedPurchaseOrderWithRevision)
      .mockReturnValueOnce(undefined); // no open CO
    spies.insert
      .mockReturnValueOnce({
        ...basePurchaseChangeOrder,
        purchaseOrderId: orderedPurchaseOrderWithRevision.id,
      }) // insert CO
      .mockReturnValueOnce(undefined); // insert CO lines

    const result = await run(
      db,
      {
        purchaseOrderId: orderedPurchaseOrderWithRevision.id,
        reason: "Mixed changes",
        lines: [
          { action: "ADD", itemId: "item-new", quantity: "50", unitPrice: "200" },
          {
            action: "MODIFY",
            originalPurchaseOrderLineId: "po-line-10",
            quantity: "120",
            unitPrice: "100",
          },
          { action: "REMOVE", originalPurchaseOrderLineId: "po-line-11" },
        ],
      },
      commandCtx,
    );

    expectOk(result);
  });

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

    const result = await run(
      db,
      {
        purchaseOrderId: "missing-po",
        reason: "N/A",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, PoNotFoundError);
  });

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

    const result = await run(
      db,
      {
        purchaseOrderId: basePurchaseOrder.id,
        reason: "N/A",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, PoNotOrderedError);
  });

  it("returns error when an open change order already exists for the purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(orderedPurchaseOrderWithRevision) // PO exists and is ORDERED
      .mockReturnValueOnce(basePurchaseChangeOrder); // open CO exists

    const result = await run(
      db,
      {
        purchaseOrderId: orderedPurchaseOrderWithRevision.id,
        reason: "Another CO",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, OpenChangeOrderExistsError);
  });

  it("returns error when creating against a DRAFT purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder); // DRAFT

    const result = await run(
      db,
      {
        purchaseOrderId: basePurchaseOrder.id,
        reason: "N/A",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, PoNotOrderedError);
  });

  it("returns error when creating against a CANCELLED purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...basePurchaseOrder, orderStatus: "CANCELLED" });

    const result = await run(
      db,
      {
        purchaseOrderId: basePurchaseOrder.id,
        reason: "N/A",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, PoNotOrderedError);
  });

  it("returns error when creating against a CLOSED purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...basePurchaseOrder, orderStatus: "CLOSED" });

    const result = await run(
      db,
      {
        purchaseOrderId: basePurchaseOrder.id,
        reason: "N/A",
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, PoNotOrderedError);
  });
});
