import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { DB } from "../generated/kysely-tailordb";
import {
  IncompatibleUnitsError,
  ItemNotActiveError,
  ItemNotFoundError,
} from "../lib/errors.generated";
import {
  baseCompany,
  baseCurrency,
  baseItem,
  basePriceList,
  basePriceRule,
  baseSupplier,
  baseUnit,
  higherBreakPriceRule,
  inactiveItem,
  inactivePriceList,
  inactivePriceRule,
  incompatibleUnit,
} from "../testing/fixtures";
import { expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./calculatePurchasePrice";

const defaultInput = {
  companyId: baseCompany.id,
  supplierId: baseSupplier.id,
  itemId: baseItem.id,
  unitId: baseUnit.id,
  currencyId: baseCurrency.id,
  quantity: 5,
  pricingDate: new Date("2024-06-01T00:00:00.000Z"),
};

describe("calculatePurchasePrice", () => {
  it("returns the best matching rule for a valid supplier item context", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(baseItem);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce(baseCurrency);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce([basePriceRule]);
    spies.select.mockReturnValueOnce([basePriceList]);

    const result = await run(db, defaultInput);
    const { priceSuggestion, matchedRule } = expectOk<{
      priceSuggestion: { ruleId: string; priceListId: string; unitPrice: number } | null;
      matchedRule: unknown;
    }>(result as never);

    expect(priceSuggestion).not.toBeNull();
    expect(priceSuggestion!.ruleId).toBe(basePriceRule.id);
    expect(priceSuggestion!.priceListId).toBe(basePriceList.id);
    expect(priceSuggestion!.unitPrice).toBe(100);
    expect(matchedRule).not.toBeNull();
  });

  it("picks the higher quantity-break rule when ordered quantity qualifies", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(baseItem);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce(baseCurrency);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce([basePriceRule, higherBreakPriceRule]);
    spies.select.mockReturnValueOnce([basePriceList]);

    const result = await run(db, { ...defaultInput, quantity: 15 });
    const { priceSuggestion } = expectOk<{
      priceSuggestion: { ruleId: string; unitPrice: number } | null;
    }>(result as never);

    expect(priceSuggestion).not.toBeNull();
    expect(priceSuggestion!.ruleId).toBe(higherBreakPriceRule.id);
    expect(priceSuggestion!.unitPrice).toBe(90);
  });

  it("ignores inactive price lists and inactive rules", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(baseItem);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce(baseCurrency);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce([inactivePriceRule]);
    spies.select.mockReturnValueOnce([inactivePriceList]);

    const result = await run(db, defaultInput);
    const { priceSuggestion } = expectOk<{
      priceSuggestion: null;
    }>(result as never);

    expect(priceSuggestion).toBeNull();
  });

  it("returns null when no active rule matches", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(baseItem);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce(baseCurrency);
    spies.select.mockReturnValueOnce(baseUnit);
    spies.select.mockReturnValueOnce([]);
    spies.select.mockReturnValueOnce([basePriceList]);

    const result = await run(db, defaultInput);
    const { priceSuggestion, matchedRule } = expectOk<{
      priceSuggestion: null;
      matchedRule: null;
    }>(result as never);

    expect(priceSuggestion).toBeNull();
    expect(matchedRule).toBeNull();
  });

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

    const result = await run(db, defaultInput);

    expectErr(result, ItemNotFoundError);
  });

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

    const result = await run(db, defaultInput);

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when units are incompatible for quantity-break comparison", async () => {
    const { db, spies } = createMockDb<DB>();
    spies.select.mockReturnValueOnce(baseItem);
    spies.select.mockReturnValueOnce(incompatibleUnit);
    spies.select.mockReturnValueOnce(baseCurrency);
    spies.select.mockReturnValueOnce(baseUnit);

    const result = await run(db, { ...defaultInput, unitId: incompatibleUnit.id });

    expectErr(result, IncompatibleUnitsError);
  });
});
