import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactiveCurrency]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseCurrencyUSD], // Source exists
      [], // Target doesn't exist
    );

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseCurrencyUSD], // Source exists and active
      [inactiveCurrency], // Target is inactive
    );

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseCurrencyUSD], [baseCurrencyUSD]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseCurrencyUSD], [baseCurrencyEUR]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    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,
    };

    mock.enqueueResults([baseCurrencyUSD], [baseCurrencyEUR], [createdRate]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        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(mock.inserts.length).toBeGreaterThan(0);
  });

  it("passes custom fields through to insert", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdRate = {
      ...baseExchangeRateUSDtoEUR,
      id: "new-rate-id",
      source: "ECB",
    };

    mock.enqueueResults([baseCurrencyUSD], [baseCurrencyEUR], [createdRate]);

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

    expect(mock.inserts[0].insertValues()).toEqual(expect.objectContaining({ source: "ECB" }));
  });
});
