/**
 * Exchange Rate Service Tests
 *
 * Tests for:
 * - API fetching with fallback
 * - Redis caching
 * - Currency conversion
 * - Fallback to hardcoded rates
 */

// Mock server-only before imports
jest.mock("server-only", () => ({}));

// Mock the cache module - must use inline factory to avoid hoisting issues
jest.mock("@/lib/cache", () => ({
  cache: {
    get: jest.fn(),
    set: jest.fn(),
    del: jest.fn(),
    getOrSet: jest.fn(),
  },
}));

// Import and cast to get typed mock reference
import { cache } from "@/lib/cache";
const mockCache = cache as jest.Mocked<typeof cache>;

// Mock the logger
jest.mock("@/lib/logger", () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    debug: jest.fn(),
  },
}));

// Mock fetch
const mockFetch = jest.fn();
global.fetch = mockFetch;

import {
  getExchangeRates,
  getExchangeRate,
  convertCurrency,
  convertFromGBP,
  getFormattedPrice,
  invalidateExchangeRateCache,
  getExchangeRateStatus,
} from "@/lib/services/exchange-rate.service";

describe("Exchange Rate Service", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("getExchangeRates", () => {
    it("should return cached rates when available", async () => {
      const cachedRates = {
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { qar: 4.9, usd: 1.35 },
        source: "cache",
        fetchedAt: "2025-12-31T12:00:00Z",
      };

      mockCache.getOrSet.mockResolvedValue(cachedRates);

      const result = await getExchangeRates();

      expect(result).toEqual(cachedRates);
      expect(mockCache.getOrSet).toHaveBeenCalledWith(
        "exchange_rates:gbp",
        expect.any(Function),
        3600,
      );
    });

    it("should fetch from API when cache miss", async () => {
      const apiResponse = {
        date: "2025-12-31",
        gbp: {
          gbp: 1,
          usd: 1.35,
          qar: 4.9,
          aed: 4.94,
          sar: 5.05,
          kwd: 0.41,
          bhd: 0.51,
          omr: 0.52,
          eur: 1.15,
        },
      };

      mockFetch.mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(apiResponse),
      });

      // Make getOrSet call the fetcher
      mockCache.getOrSet.mockImplementation(async (key, fetcher) => {
        return fetcher();
      });

      const result = await getExchangeRates();

      expect(result.source).toBe("api");
      expect(result.rates.qar).toBe(4.9);
      expect(result.rates.usd).toBe(1.35);
    });

    it("should use fallback URL when primary fails", async () => {
      const apiResponse = {
        date: "2025-12-31",
        gbp: { gbp: 1, qar: 4.9 },
      };

      mockFetch
        .mockRejectedValueOnce(new Error("Network error"))
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve(apiResponse),
        });

      mockCache.getOrSet.mockImplementation(async (key, fetcher) => {
        return fetcher();
      });

      const result = await getExchangeRates();

      expect(mockFetch).toHaveBeenCalledTimes(2);
      expect(result.source).toBe("api");
    });

    it("should use fallback rates when both APIs fail", async () => {
      mockFetch.mockRejectedValue(new Error("All APIs failed"));

      mockCache.getOrSet.mockImplementation(async (key, fetcher) => {
        return fetcher();
      });

      const result = await getExchangeRates();

      expect(result.source).toBe("fallback");
      expect(result.rates.gbp).toBe(1);
      expect(result.rates.qar).toBe(4.62); // Fallback rate
    });
  });

  describe("getExchangeRate", () => {
    beforeEach(() => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, usd: 1.35, qar: 4.9, eur: 1.15 },
        source: "cache",
        fetchedAt: "2025-12-31T12:00:00Z",
      });
    });

    it("should return 1 for same currency", async () => {
      const rate = await getExchangeRate("GBP", "GBP");
      expect(rate).toBe(1);
    });

    it("should return rate for GBP to other currency", async () => {
      const rate = await getExchangeRate("GBP", "QAR");
      expect(rate).toBe(4.9);
    });

    it("should calculate inverse rate for other to GBP", async () => {
      const rate = await getExchangeRate("QAR", "GBP");
      expect(rate).toBeCloseTo(1 / 4.9, 5);
    });

    it("should calculate cross-rate for non-GBP pairs", async () => {
      const rate = await getExchangeRate("USD", "QAR");
      // QAR/GBP = 4.9, USD/GBP = 1.35
      // USD to QAR = 4.9 / 1.35 = 3.63
      expect(rate).toBeCloseTo(4.9 / 1.35, 2);
    });
  });

  describe("convertCurrency", () => {
    beforeEach(() => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, usd: 1.35, qar: 4.9 },
        source: "cache",
        fetchedAt: "2025-12-31T12:00:00Z",
      });
    });

    it("should return same amount for same currency", async () => {
      const result = await convertCurrency(100, "GBP", "GBP");
      expect(result).toBe(100);
    });

    it("should convert GBP to QAR correctly", async () => {
      const result = await convertCurrency(100, "GBP", "QAR");
      expect(result).toBe(490); // 100 * 4.9
    });

    it("should round to 2 decimal places", async () => {
      const result = await convertCurrency(33.33, "GBP", "QAR");
      expect(result).toBe(163.32); // 33.33 * 4.9 = 163.317 -> 163.32
    });
  });

  describe("convertFromGBP", () => {
    beforeEach(() => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, qar: 4.9 },
        source: "cache",
        fetchedAt: "2025-12-31T12:00:00Z",
      });
    });

    it("should convert GBP to target currency", async () => {
      const result = await convertFromGBP(9, "QAR");
      expect(result).toBe(44.1); // 9 * 4.9
    });
  });

  describe("getFormattedPrice", () => {
    beforeEach(() => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, usd: 1.35, qar: 4.9 },
        source: "cache",
        fetchedAt: "2025-12-31T12:00:00Z",
      });
    });

    it("should format GBP with pound symbol before amount", async () => {
      const result = await getFormattedPrice(100, "GBP");
      expect(result.symbol).toBe("£");
      expect(result.amount).toBe(100);
      expect(result.formatted).toBe("£100");
    });

    it("should format USD with dollar symbol before amount", async () => {
      const result = await getFormattedPrice(100, "USD");
      expect(result.symbol).toBe("$");
      expect(result.amount).toBe(135); // 100 * 1.35
      expect(result.formatted).toBe("$135");
    });

    it("should format QAR with symbol after amount (RTL)", async () => {
      const result = await getFormattedPrice(100, "QAR");
      expect(result.symbol).toBe("ر.ق");
      expect(result.amount).toBe(490); // 100 * 4.9
      expect(result.formatted).toBe("490 ر.ق");
    });
  });

  describe("invalidateExchangeRateCache", () => {
    it("should delete the cache key", async () => {
      await invalidateExchangeRateCache();
      expect(mockCache.del).toHaveBeenCalledWith("exchange_rates:gbp");
    });
  });

  describe("getExchangeRateStatus", () => {
    it("should return healthy status when using API", async () => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, usd: 1.35, qar: 4.9 },
        source: "api",
        fetchedAt: "2025-12-31T12:00:00Z",
      });

      const status = await getExchangeRateStatus();

      expect(status.healthy).toBe(true);
      expect(status.source).toBe("api");
      expect(status.rateCount).toBe(3);
    });

    it("should return unhealthy status when using fallback", async () => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1 },
        source: "fallback",
        fetchedAt: "2025-12-31T12:00:00Z",
      });

      const status = await getExchangeRateStatus();

      expect(status.healthy).toBe(false);
      expect(status.source).toBe("fallback");
    });
  });

  describe("seat price conversion", () => {
    // Specific test for the seat purchase use case from PROMPT
    beforeEach(() => {
      mockCache.getOrSet.mockResolvedValue({
        date: "2025-12-31",
        baseCurrency: "GBP",
        rates: { gbp: 1, qar: 4.9 },
        source: "api",
        fetchedAt: "2025-12-31T12:00:00Z",
      });
    });

    it("should convert £9 seat price to QAR correctly", async () => {
      const seatPriceGBP = 9;
      const result = await convertFromGBP(seatPriceGBP, "QAR");

      // £9 * 4.9 = 44.1 QAR
      expect(result).toBe(44.1);
    });

    it("should format seat purchase total for 4 seats", async () => {
      const seatPriceGBP = 9;
      const quantity = 4;
      const totalGBP = seatPriceGBP * quantity; // £36

      const result = await getFormattedPrice(totalGBP, "QAR");

      // £36 * 4.9 = 176.4 QAR
      expect(result.amount).toBe(176.4);
      expect(result.formatted).toBe("176.4 ر.ق");
    });
  });
});
