import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateCurrencyCodeError,
  InvalidDecimalPlacesError,
  InvalidIsoCodeError,
} from "../lib/errors.generated";
import { baseCurrencyUSD } from "../testing/fixtures";
import { run } from "./createCurrency";

describe("createCurrency", () => {
  // Error cases
  it("throws when ISO code is invalid format", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, {
      code: "us", // Should be 3 uppercase letters
      name: "US Dollar",
      symbol: "$",
      decimalPlaces: 2,
    });

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

  it("throws when ISO code already exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(baseCurrencyUSD);

    const result = await run(db, {
      code: "USD",
      name: "US Dollar",
      symbol: "$",
      decimalPlaces: 2,
    });

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

  it("throws when decimal places is negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined); // No existing currency

    const result = await run(db, {
      code: "XXX",
      name: "Test Currency",
      symbol: "X",
      decimalPlaces: -1,
    });

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

  it("throws when decimal places exceeds maximum", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined); // No existing currency

    const result = await run(db, {
      code: "XXX",
      name: "Test Currency",
      symbol: "X",
      decimalPlaces: 5,
    });

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

  // Success cases
  it("creates first currency as base currency", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdCurrency = {
      id: "new-currency-id",
      code: "USD",
      name: "US Dollar",
      symbol: "$",
      decimalPlaces: 2,
      isBaseCurrency: true,
      isActive: true,
      createdAt: new Date("2024-01-15T00:00:00.000Z"),
      updatedAt: null,
    };

    spies.select
      .mockReturnValueOnce(undefined) // No existing with same code
      .mockReturnValueOnce(undefined); // No existing currencies (first currency)
    spies.insert.mockReturnValue(createdCurrency);

    const result = await run(db, {
      code: "USD",
      name: "US Dollar",
      symbol: "$",
      decimalPlaces: 2,
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.currency.code).toBe("USD");
      expect(result.value.currency.isBaseCurrency).toBe(true);
      expect(result.value.currency.isActive).toBe(true);
    }
  });

  it("creates subsequent currency as non-base", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdCurrency = {
      id: "new-currency-id",
      code: "EUR",
      name: "Euro",
      symbol: "€",
      decimalPlaces: 2,
      isBaseCurrency: false,
      isActive: true,
      createdAt: new Date("2024-01-15T00:00:00.000Z"),
      updatedAt: null,
    };

    spies.select
      .mockReturnValueOnce(undefined) // No existing with same code
      .mockReturnValueOnce(baseCurrencyUSD); // Existing currency exists
    spies.insert.mockReturnValue(createdCurrency);

    const result = await run(db, {
      code: "EUR",
      name: "Euro",
      symbol: "€",
      decimalPlaces: 2,
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.currency.code).toBe("EUR");
      expect(result.value.currency.isBaseCurrency).toBe(false);
    }
  });

  it("passes custom fields through to insert", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdCurrency = {
      ...baseCurrencyUSD,
      id: "new-currency-id",
      code: "GBP",
      region: "Europe",
    };

    spies.select
      .mockReturnValueOnce(undefined) // No existing with same code
      .mockReturnValueOnce(undefined); // No existing currencies
    spies.insert.mockReturnValue(createdCurrency);

    await run(db, {
      code: "GBP",
      name: "British Pound",
      symbol: "£",
      decimalPlaces: 2,
      region: "Europe",
    });

    expect(spies.values).toHaveBeenNthCalledWith(1, expect.objectContaining({ region: "Europe" }));
  });
});
