/**
 * User Currency Detection Service Tests
 *
 * Tests for:
 * - IP geolocation detection
 * - Accept-Language header parsing
 * - Timezone-based detection
 * - Profile preference lookup
 * - Fallback chain behavior
 */

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

// Mock the cache module
jest.mock("@/lib/cache", () => ({
  cache: {
    get: jest.fn(),
    set: jest.fn(),
    del: jest.fn(),
  },
}));

// 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;

jest.mock("@/lib/db");

// eslint-disable-next-line @typescript-eslint/no-require-imports
const { prisma: mockPrisma } = require("@/lib/db");

import { cache } from "@/lib/cache";
const mockCache = cache as jest.Mocked<typeof cache>;

import {
  getUserCurrency,
  extractClientInfo,
} from "@/lib/services/user-currency.service";

import {
  getCurrencyFromCountry,
  getCurrencyConfig,
  formatCurrencyAmount,
  isSupportedCurrency,
  COUNTRY_TO_CURRENCY,
  CURRENCY_CONFIG,
} from "@/lib/config/currencies.config";

describe("User Currency Detection Service", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("getCurrencyFromCountry", () => {
    it("should return QAR for Qatar", () => {
      expect(getCurrencyFromCountry("QA")).toBe("QAR");
    });

    it("should return AED for UAE", () => {
      expect(getCurrencyFromCountry("AE")).toBe("AED");
    });

    it("should return GBP for UK", () => {
      expect(getCurrencyFromCountry("GB")).toBe("GBP");
    });

    it("should return EUR for Germany", () => {
      expect(getCurrencyFromCountry("DE")).toBe("EUR");
    });

    it("should be case-insensitive", () => {
      expect(getCurrencyFromCountry("qa")).toBe("QAR");
      expect(getCurrencyFromCountry("Qa")).toBe("QAR");
    });

    it("should return null for unknown country", () => {
      expect(getCurrencyFromCountry("ZZ")).toBeNull();
    });
  });

  describe("getCurrencyConfig", () => {
    it("should return config for GBP", () => {
      const config = getCurrencyConfig("GBP");
      expect(config.symbol).toBe("£");
      expect(config.symbolPosition).toBe("before");
      expect(config.isRTL).toBe(false);
    });

    it("should return config for QAR", () => {
      const config = getCurrencyConfig("QAR");
      expect(config.symbol).toBe("ر.ق");
      expect(config.symbolPosition).toBe("after");
      expect(config.isRTL).toBe(true);
    });

    it("should have correct decimal places for KWD", () => {
      const config = getCurrencyConfig("KWD");
      expect(config.decimalPlaces).toBe(3);
    });
  });

  describe("formatCurrencyAmount", () => {
    it("should format GBP with symbol before", () => {
      expect(formatCurrencyAmount(1234.56, "GBP")).toBe("£1,234.56");
    });

    it("should format QAR with symbol after", () => {
      expect(formatCurrencyAmount(1234.56, "QAR")).toBe("1,234.56 ر.ق");
    });

    it("should format USD with symbol before", () => {
      expect(formatCurrencyAmount(99.99, "USD")).toBe("$99.99");
    });

    it("should format KWD with 3 decimal places", () => {
      expect(formatCurrencyAmount(100.5, "KWD")).toBe("100.500 د.ك");
    });

    it("should format without decimals when option is set", () => {
      expect(
        formatCurrencyAmount(1234.56, "GBP", { showDecimals: false }),
      ).toBe("£1,235");
    });
  });

  describe("isSupportedCurrency", () => {
    it("should return true for supported currencies", () => {
      expect(isSupportedCurrency("GBP")).toBe(true);
      expect(isSupportedCurrency("QAR")).toBe(true);
      expect(isSupportedCurrency("USD")).toBe(true);
    });

    it("should return false for unsupported currencies", () => {
      expect(isSupportedCurrency("JPY")).toBe(false);
      expect(isSupportedCurrency("CNY")).toBe(false);
      expect(isSupportedCurrency("INVALID")).toBe(false);
    });
  });

  describe("getUserCurrency", () => {
    describe("Profile preference (highest priority)", () => {
      it("should return profile currency when set", async () => {
        mockPrisma.users.findUnique.mockResolvedValue({
          preferredCurrency: "AED",
        });

        const result = await getUserCurrency({
          userId: "user-123",
          ipAddress: "1.2.3.4",
          acceptLanguage: "en-US",
        });

        expect(result.currency).toBe("AED");
        expect(result.source).toBe("profile");
        expect(result.confidence).toBe("high");
      });

      it("should skip profile if user not found", async () => {
        mockPrisma.users.findUnique.mockResolvedValue(null);
        mockCache.get.mockResolvedValue("QA");

        mockFetch.mockResolvedValue({
          ok: true,
          json: () => Promise.resolve({ status: "success", countryCode: "QA" }),
        });

        const result = await getUserCurrency({
          userId: "user-123",
          ipAddress: "1.2.3.4",
        });

        expect(result.source).not.toBe("profile");
      });
    });

    describe("IP geolocation (second priority)", () => {
      beforeEach(() => {
        mockPrisma.users.findUnique.mockResolvedValue(null);
        mockCache.get.mockResolvedValue(null);
      });

      it("should return currency from cached IP result", async () => {
        // When cache.get returns a country code, should use it
        mockCache.get.mockResolvedValue("SA");

        const result = await getUserCurrency({
          ipAddress: "8.8.8.8",
        });

        expect(result.currency).toBe("SAR");
        expect(result.source).toBe("ip");
        expect(result.countryCode).toBe("SA");
        expect(result.confidence).toBe("high");
      });

      it("should use cached IP result", async () => {
        mockCache.get.mockResolvedValue("AE");

        const result = await getUserCurrency({
          ipAddress: "1.2.3.4",
        });

        expect(result.currency).toBe("AED");
        expect(mockFetch).not.toHaveBeenCalled();
      });

      it("should skip localhost IP", async () => {
        mockCache.get.mockResolvedValue(null);

        const result = await getUserCurrency({
          ipAddress: "127.0.0.1",
          acceptLanguage: "ar-QA",
        });

        expect(mockFetch).not.toHaveBeenCalled();
        expect(result.source).toBe("locale");
      });

      it("should skip private IP ranges", async () => {
        mockCache.get.mockResolvedValue(null);

        const result = await getUserCurrency({
          ipAddress: "192.168.1.1",
          timezone: "Asia/Qatar",
        });

        expect(mockFetch).not.toHaveBeenCalled();
        expect(result.source).toBe("timezone");
      });
    });

    describe("Accept-Language (third priority)", () => {
      beforeEach(() => {
        mockPrisma.users.findUnique.mockResolvedValue(null);
        mockCache.get.mockResolvedValue(null);
        mockFetch.mockRejectedValue(new Error("API failed"));
      });

      it("should detect currency from ar-QA locale", async () => {
        const result = await getUserCurrency({
          acceptLanguage: "ar-QA,ar;q=0.9,en;q=0.8",
        });

        expect(result.currency).toBe("QAR");
        expect(result.source).toBe("locale");
        expect(result.confidence).toBe("medium");
      });

      it("should detect currency from en-US locale", async () => {
        const result = await getUserCurrency({
          acceptLanguage: "en-US,en;q=0.9",
        });

        expect(result.currency).toBe("USD");
        expect(result.source).toBe("locale");
      });

      it("should detect currency from de-DE locale", async () => {
        const result = await getUserCurrency({
          acceptLanguage: "de-DE,de;q=0.9",
        });

        expect(result.currency).toBe("EUR");
      });
    });

    describe("Timezone (fourth priority)", () => {
      beforeEach(() => {
        mockPrisma.users.findUnique.mockResolvedValue(null);
        mockCache.get.mockResolvedValue(null);
        mockFetch.mockRejectedValue(new Error("API failed"));
      });

      it("should detect currency from Asia/Qatar timezone", async () => {
        const result = await getUserCurrency({
          timezone: "Asia/Qatar",
        });

        expect(result.currency).toBe("QAR");
        expect(result.source).toBe("timezone");
        expect(result.confidence).toBe("medium");
      });

      it("should detect currency from Europe/London timezone", async () => {
        const result = await getUserCurrency({
          timezone: "Europe/London",
        });

        expect(result.currency).toBe("GBP");
      });

      it("should detect currency from America/New_York timezone", async () => {
        const result = await getUserCurrency({
          timezone: "America/New_York",
        });

        expect(result.currency).toBe("USD");
      });
    });

    describe("Default fallback", () => {
      beforeEach(() => {
        mockPrisma.users.findUnique.mockResolvedValue(null);
        mockCache.get.mockResolvedValue(null);
        mockFetch.mockRejectedValue(new Error("API failed"));
      });

      it("should return QAR as default when all detection fails", async () => {
        const result = await getUserCurrency({});

        expect(result.currency).toBe("QAR");
        expect(result.source).toBe("default");
        expect(result.confidence).toBe("low");
      });
    });
  });

  describe("extractClientInfo", () => {
    function createMockRequest(headersObj: Record<string, string>): Request {
      const headers = new Headers();
      for (const [key, value] of Object.entries(headersObj)) {
        headers.set(key, value);
      }
      return {
        headers,
      } as unknown as Request;
    }

    it("should extract IP from x-forwarded-for", () => {
      const request = createMockRequest({
        "x-forwarded-for": "1.2.3.4, 5.6.7.8",
      });

      const info = extractClientInfo(request);
      expect(info.ipAddress).toBe("1.2.3.4");
    });

    it("should extract IP from x-real-ip", () => {
      const request = createMockRequest({ "x-real-ip": "1.2.3.4" });

      const info = extractClientInfo(request);
      expect(info.ipAddress).toBe("1.2.3.4");
    });

    it("should extract Accept-Language", () => {
      const request = createMockRequest({
        "accept-language": "ar-QA,ar;q=0.9",
      });

      const info = extractClientInfo(request);
      expect(info.acceptLanguage).toBe("ar-QA,ar;q=0.9");
    });

    it("should extract timezone from custom header", () => {
      const request = createMockRequest({ "x-timezone": "Asia/Qatar" });

      const info = extractClientInfo(request);
      expect(info.timezone).toBe("Asia/Qatar");
    });
  });

  describe("Currency configuration completeness", () => {
    it("should have all GCC countries mapped", () => {
      const gccCountries = ["QA", "AE", "SA", "KW", "BH", "OM"];
      for (const country of gccCountries) {
        expect(COUNTRY_TO_CURRENCY[country]).toBeDefined();
      }
    });

    it("should have config for all mapped currencies", () => {
      const currencies = new Set(Object.values(COUNTRY_TO_CURRENCY));
      for (const currency of currencies) {
        expect(CURRENCY_CONFIG[currency]).toBeDefined();
      }
    });

    it("should have Arabic names for all currencies", () => {
      for (const currency of Object.values(CURRENCY_CONFIG)) {
        expect(currency.nameAr).toBeDefined();
        expect(currency.nameAr.length).toBeGreaterThan(0);
      }
    });
  });
});
