import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  DuplicateRulePrecedenceError,
  InvalidRuleConfigurationError,
  ItemNotActiveError,
  ItemNotFoundError,
  PriceRuleNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import {
  baseCurrency,
  baseItem,
  basePriceRule,
  baseUnit,
  inactiveItem,
  inactivePriceRule,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./activatePurchasePriceRule";

describe("activatePurchasePriceRule", () => {
  it("activates an inactive price rule", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(inactivePriceRule)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit)
      .mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce({ ...inactivePriceRule, status: "ACTIVE" });

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);
    const value = expectOk<{ purchasePriceRule: typeof inactivePriceRule }>(result);

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

  it("returns success when the rule is already active", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePriceRule);

    const result = await run(db, { id: basePriceRule.id }, commandCtx);
    const value = expectOk<{ purchasePriceRule: typeof basePriceRule }>(result);

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

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

    const result = await run(db, { id: "missing-rule" }, commandCtx);

    expectErr(result, PriceRuleNotFoundError);
  });

  it("returns error when activation would conflict with another active rule", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(inactivePriceRule)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit)
      .mockReturnValueOnce({ ...basePriceRule, id: "other-rule" });

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

    expectErr(result, DuplicateRulePrecedenceError);
  });

  it("returns error when rule configuration is invalid at activation time", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce({
        ...inactivePriceRule,
        effectiveFrom: new Date("2024-02-01T00:00:00.000Z"),
        effectiveTo: new Date("2024-01-01T00:00:00.000Z"),
      })
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit);

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

    expectErr(result, InvalidRuleConfigurationError);
  });

  it("returns error when the referenced item no longer exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(inactivePriceRule).mockReturnValueOnce(undefined);

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

    expectErr(result, ItemNotFoundError);
  });

  it("returns error when the referenced item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(inactivePriceRule).mockReturnValueOnce(inactiveItem);

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when the referenced currency no longer exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(inactivePriceRule)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(undefined);

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

    expectErr(result, CurrencyNotFoundError);
  });

  it("returns error when the referenced unit no longer exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(inactivePriceRule)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(undefined);

    const result = await run(db, { id: inactivePriceRule.id }, commandCtx);

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