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

const itemQ = mockItemQueries({ item: baseItem });

describe("activatePurchasePriceRule", () => {
  it("activates an inactive price rule", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule], [], [{ ...inactivePriceRule, status: "ACTIVE" }]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );
    const value = expectOk(result);

    expect(value.purchasePriceRule.status).toBe("ACTIVE");
  });

  it("returns success when the rule is already active", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([basePriceRule]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: basePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );
    const value = expectOk(result);

    expect(value.purchasePriceRule).toEqual(basePriceRule);
  });

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

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: "missing-rule" },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );

    expectErr(result, PriceRuleNotFoundError);
  });

  it("returns error when activation would conflict with another active rule", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule], [{ ...basePriceRule, id: "other-rule" }]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );

    expectErr(result, DuplicateRulePrecedenceError);
  });

  it("returns error when rule configuration is invalid at activation time", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([
      {
        ...inactivePriceRule,
        effectiveFrom: new Date("2024-02-01T00:00:00.000Z"),
        effectiveTo: new Date("2024-01-01T00:00:00.000Z"),
      },
    ]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );

    expectErr(result, InvalidRuleConfigurationError);
  });

  it("returns error when the referenced item no longer exists", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        mockItemQueries(),
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );

    expectErr(result, ItemNotFoundError);
  });

  it("returns error when the referenced item is inactive", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        mockItemQueries({ item: inactiveItem }),
        mockPrimitivesQueries({ currency: baseCurrency, unit: baseUnit }),
      ),
    );

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when the referenced currency no longer exists", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ unit: baseUnit }),
      ),
    );

    expectErr(result, CurrencyNotFoundError);
  });

  it("returns error when the referenced unit no longer exists", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePriceRule]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { id: inactivePriceRule.id },
        commandCtx,
        itemQ,
        mockPrimitivesQueries({ currency: baseCurrency }),
      ),
    );

    expectErr(result, UnitNotFoundError);
  });
});
