import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  DuplicateRulePrecedenceError,
  InvalidEffectiveRangeError,
  InvalidMinimumQuantityError,
  InvalidUnitPriceError,
  ItemNotActiveError,
  ItemNotFoundError,
  PriceListNotFoundError,
  UnitNotFoundError,
} from "../lib/errors.generated";
import {
  baseCurrency,
  baseItem,
  basePriceList,
  basePriceRule,
  baseUnit,
  inactiveItem,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
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;

describe("createPurchasePriceRule", () => {
  it("creates an active purchase price rule with valid references", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit)
      .mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(basePriceRule);

    const result = await run(db, baseInput, commandCtx);

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

  it("returns error when price list does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);
    expectErr(await run(db, baseInput, commandCtx), PriceListNotFoundError);
  });

  it("returns error when item does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePriceList).mockReturnValueOnce(undefined);
    expectErr(await run(db, baseInput, commandCtx), ItemNotFoundError);
  });

  it("returns error when item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePriceList).mockReturnValueOnce(inactiveItem);
    expectErr(await run(db, baseInput, commandCtx), ItemNotActiveError);
  });

  it("returns error when currency does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(undefined);
    expectErr(await run(db, baseInput, commandCtx), CurrencyNotFoundError);
  });

  it("returns error when unit does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(undefined);
    expectErr(await run(db, baseInput, commandCtx), UnitNotFoundError);
  });

  it("returns error when unit price is not positive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit);
    expectErr(await run(db, { ...baseInput, unitPrice: 0 }, commandCtx), InvalidUnitPriceError);
  });

  it("returns error when minimum quantity is negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit);
    expectErr(
      await run(db, { ...baseInput, minimumQuantity: -1 }, commandCtx),
      InvalidMinimumQuantityError,
    );
  });

  it("returns error when effective range is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit);
    expectErr(
      await run(
        db,
        { ...baseInput, effectiveTo: new Date("2023-12-31T00:00:00.000Z") },
        commandCtx,
      ),
      InvalidEffectiveRangeError,
    );
  });

  it("returns error when another rule already has the same precedence", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(basePriceList)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseCurrency)
      .mockReturnValueOnce(baseUnit)
      .mockReturnValueOnce({ id: "existing-rule" });
    expectErr(await run(db, baseInput, commandCtx), DuplicateRulePrecedenceError);
  });
});
