import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import { Transaction } from "../generated/kysely-tailordb";
import {
  CurrencyNotFoundError,
  InactiveCurrencyError,
  InvalidExchangeRateError,
  SameCurrencyPairError,
} from "../lib/errors.generated";
import {
  baseCurrencyEUR,
  baseCurrencyUSD,
  baseExchangeRateUSDtoEUR,
  inactiveCurrency,
} from "../testing/fixtures";
import { run } from "./createExchangeRate";

describe("createExchangeRate", () => {
  // Error cases
  it("throws when source currency doesn't exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined);

    const result = await run(db, {
      sourceCurrencyId: "nonexistent-currency",
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CurrencyNotFoundError);
    }
  });

  it("throws when source currency is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(inactiveCurrency);

    const result = await run(db, {
      sourceCurrencyId: inactiveCurrency.id,
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InactiveCurrencyError);
    }
  });

  it("throws when target currency doesn't exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCurrencyUSD) // Source exists
      .mockReturnValueOnce(undefined); // Target doesn't exist

    const result = await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: "nonexistent-currency",
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CurrencyNotFoundError);
    }
  });

  it("throws when target currency is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCurrencyUSD) // Source exists and active
      .mockReturnValueOnce(inactiveCurrency); // Target is inactive

    const result = await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: inactiveCurrency.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InactiveCurrencyError);
    }
  });

  it("throws when source and target are the same", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(baseCurrencyUSD);

    const result = await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: baseCurrencyUSD.id,
      rate: 1.0,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(SameCurrencyPairError);
    }
  });

  it("throws when rate is not positive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCurrencyUSD).mockReturnValueOnce(baseCurrencyEUR);

    const result = await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidExchangeRateError);
    }
  });

  // Success cases
  it("creates exchange rate successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdRate = {
      id: "new-rate-id",
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15T00:00:00.000Z"),
      createdAt: new Date("2024-01-15T00:00:00.000Z"),
      updatedAt: null,
    };

    spies.select.mockReturnValueOnce(baseCurrencyUSD).mockReturnValueOnce(baseCurrencyEUR);
    spies.insert.mockReturnValue(createdRate);

    const result = await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.exchangeRate.rate).toBe(0.92);
      expect(result.value.exchangeRate.sourceCurrencyId).toBe(baseCurrencyUSD.id);
      expect(result.value.exchangeRate.targetCurrencyId).toBe(baseCurrencyEUR.id);
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("passes custom fields through to insert", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdRate = {
      ...baseExchangeRateUSDtoEUR,
      id: "new-rate-id",
      source: "ECB",
    };

    spies.select.mockReturnValueOnce(baseCurrencyUSD).mockReturnValueOnce(baseCurrencyEUR);
    spies.insert.mockReturnValue(createdRate);

    await run(db, {
      sourceCurrencyId: baseCurrencyUSD.id,
      targetCurrencyId: baseCurrencyEUR.id,
      rate: 0.92,
      effectiveDate: new Date("2024-01-15"),
      source: "ECB",
    });

    expect(spies.values).toHaveBeenNthCalledWith(1, expect.objectContaining({ source: "ECB" }));
  });
});
