import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  DuplicateRulePrecedenceError,
  InvalidEffectiveRangeError,
  InvalidMinimumQuantityError,
  InvalidUnitPriceError,
  ItemNotActiveError,
  ItemNotFoundError,
  PriceListNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseCurrency,
  baseItem,
  basePriceList,
  basePriceRule,
  baseUnit,
  inactiveItem,
} from "../testing/fixtures";
import { mockItemQueries, mockPrimitivesQueries } from "../testing/moduleMocks";
import { run } from "./createPurchasePriceRule";

const baseInput = {
  priceListId: basePriceRule.priceListId,
  itemId: basePriceRule.itemId,
  currencyId: basePriceRule.currencyId,
  unitId: basePriceRule.unitId,
  minimumQuantity: basePriceRule.minimumQuantity,
  unitPrice: basePriceRule.unitPrice,
  effectiveFrom: basePriceRule.effectiveFrom,
} as const;

const itemQ = mockItemQueries({ item: baseItem });
const primQ = mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit });

describe("createPurchasePriceRule", () => {
  it("creates an active purchase price rule with valid references", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList], [], [basePriceRule]);

    const result = await mock.withTx((trx) => run(trx, baseInput, commandCtx, itemQ, primQ));

    expect(expectOk(result).purchasePriceRule.status).toBe("ACTIVE");
  });

  it("returns error when price list does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);
    expectErr(
      await mock.withTx((trx) => run(trx, baseInput, commandCtx, itemQ, primQ)),
      PriceListNotFoundError,
    );
  });

  it("returns error when item does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) => run(trx, baseInput, commandCtx, mockItemQueries(), primQ)),
      ItemNotFoundError,
    );
  });

  it("returns error when item is inactive", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(trx, baseInput, commandCtx, mockItemQueries({ item: inactiveItem }), primQ),
      ),
      ItemNotActiveError,
    );
  });

  it("returns error when currency does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(trx, baseInput, commandCtx, itemQ, mockPrimitivesQueries({ unit: baseUnit })),
      ),
      CurrencyNotFoundError,
    );
  });

  it("returns error when unit does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(trx, baseInput, commandCtx, itemQ, mockPrimitivesQueries({ currency: baseCurrency })),
      ),
      UnitNotFoundError,
    );
  });

  it("returns error when unit price is not positive", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(trx, { ...baseInput, unitPrice: "0" }, commandCtx, itemQ, primQ),
      ),
      InvalidUnitPriceError,
    );
  });

  it("returns error when minimum quantity is negative", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(trx, { ...baseInput, minimumQuantity: "-1" }, commandCtx, itemQ, primQ),
      ),
      InvalidMinimumQuantityError,
    );
  });

  it("returns error when effective range is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList]);
    expectErr(
      await mock.withTx((trx) =>
        run(
          trx,
          { ...baseInput, effectiveTo: new Date("2023-12-31T00:00:00.000Z") },
          commandCtx,
          itemQ,
          primQ,
        ),
      ),
      InvalidEffectiveRangeError,
    );
  });

  it("returns error when another rule already has the same precedence", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceList], [{ id: "existing-rule" }]);
    expectErr(
      await mock.withTx((trx) => run(trx, baseInput, commandCtx, itemQ, primQ)),
      DuplicateRulePrecedenceError,
    );
  });
});
