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

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

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

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

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    const createdCurrency = {
      id: "new-currency-id",
      code: "USD",
      name: "US Dollar",
      symbol: "$",
      decimalPlaces: 2,
      isBaseCurrency: true,
      status: "ACTIVE" as const,
      createdAt: new Date("2024-01-15T00:00:00.000Z"),
      updatedAt: null,
    };

    mock.enqueueResults(
      [], // No existing with same code
      [], // No existing currencies (first currency)
      [createdCurrency],
    );

    const result = await mock.withTx((trx) =>
      run(trx, {
        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.status).toBe("ACTIVE");
    }
  });

  it("creates subsequent currency as non-base", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdCurrency = {
      id: "new-currency-id",
      code: "EUR",
      name: "Euro",
      symbol: "€",
      decimalPlaces: 2,
      isBaseCurrency: false,
      status: "ACTIVE" as const,
      createdAt: new Date("2024-01-15T00:00:00.000Z"),
      updatedAt: null,
    };

    mock.enqueueResults(
      [], // No existing with same code
      [baseCurrencyUSD], // Existing currency exists
      [createdCurrency],
    );

    const result = await mock.withTx((trx) =>
      run(trx, {
        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 mock = createKyselyMock<Namespace["main-db"]>();
    const createdCurrency = {
      ...baseCurrencyUSD,
      id: "new-currency-id",
      code: "GBP",
      region: "Europe",
    };

    mock.enqueueResults(
      [], // No existing with same code
      [], // No existing currencies
      [createdCurrency],
    );

    await mock.withTx((trx) =>
      run(trx, {
        code: "GBP",
        name: "British Pound",
        symbol: "£",
        decimalPlaces: 2,
        region: "Europe",
      }),
    );

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