import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  NegativeBilledQuantityError,
  PurchaseOrderLineNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { basePurchaseOrderLine, orderedPurchaseOrder } from "../testing/fixtures";
import { run } from "./recalculatePurchaseOrderBillingStatus";

describe("recalculatePurchaseOrderBillingStatus", () => {
  it("updates billingStatus to partially billed from supplied line quantities", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [basePurchaseOrderLine],
      [],
      [orderedPurchaseOrder],
      [{ ...basePurchaseOrderLine, billedQuantity: "4" }],
      [{ ...orderedPurchaseOrder, billingStatus: "PARTIALLY_BILLED" }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { lineBillings: [{ purchaseOrderLineId: basePurchaseOrderLine.id, billedQuantity: "4" }] },
        commandCtx,
      ),
    );

    const value = expectOk(result);
    expect(value.purchaseOrders[0].billingStatus).toBe("PARTIALLY_BILLED");
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({ billedQuantity: "4" }),
    );
  });

  it("updates billingStatus to billed when supplied quantities cover the order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [basePurchaseOrderLine],
      [],
      [orderedPurchaseOrder],
      [{ ...basePurchaseOrderLine, billedQuantity: "10" }],
      [{ ...orderedPurchaseOrder, billingStatus: "BILLED" }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          lineBillings: [{ purchaseOrderLineId: basePurchaseOrderLine.id, billedQuantity: "10" }],
        },
        commandCtx,
      ),
    );

    expect(expectOk(result).purchaseOrders[0].billingStatus).toBe("BILLED");
  });

  it("resets billingStatus to not billed when supplied quantities are zero", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [basePurchaseOrderLine],
      [],
      [orderedPurchaseOrder],
      [{ ...basePurchaseOrderLine, billedQuantity: "0" }],
      [{ ...orderedPurchaseOrder, billingStatus: "NOT_BILLED" }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { lineBillings: [{ purchaseOrderLineId: basePurchaseOrderLine.id, billedQuantity: "0" }] },
        commandCtx,
      ),
    );

    expect(expectOk(result).purchaseOrders[0].billingStatus).toBe("NOT_BILLED");
  });

  it("returns no updated purchase orders when no line progress is supplied", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { lineBillings: [] }, commandCtx));

    expect(expectOk(result).purchaseOrders).toEqual([]);
    expect(mock.updates).toHaveLength(0);
  });

  it("returns error when billed quantity is negative", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          lineBillings: [{ purchaseOrderLineId: basePurchaseOrderLine.id, billedQuantity: "-1" }],
        },
        commandCtx,
      ),
    );

    expectErr(result, NegativeBilledQuantityError);
  });

  it("returns error when a purchase-order line does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { lineBillings: [{ purchaseOrderLineId: "missing-line", billedQuantity: "1" }] },
        commandCtx,
      ),
    );

    expectErr(result, PurchaseOrderLineNotFoundError);
  });
});
