/**
 * Unit Tests for Geolocation Helper
 *
 * Tests IP address extraction, geolocation lookup, and helper functions
 */

import { getIPFromRequest, getLocationFromIP } from "@/lib/geolocation";

// Mock fetch for API calls
global.fetch = jest.fn();

describe("Geolocation Helper", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("getIPFromRequest", () => {
    it("should extract IP from x-forwarded-for header", () => {
      const mockRequest = {
        headers: new Map([
          ["x-forwarded-for", "203.0.113.195, 198.51.100.178"],
        ]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("203.0.113.195");
    });

    it("should extract IP from x-real-ip header", () => {
      const mockRequest = {
        headers: new Map([["x-real-ip", "198.51.100.42"]]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("198.51.100.42");
    });

    it("should extract IP from cf-connecting-ip (Cloudflare)", () => {
      const mockRequest = {
        headers: new Map([["cf-connecting-ip", "203.0.113.45"]]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("203.0.113.45");
    });

    it("should extract IP from x-vercel-forwarded-for (Vercel)", () => {
      const mockRequest = {
        headers: new Map([["x-vercel-forwarded-for", "198.51.100.99"]]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("198.51.100.99");
    });

    it("should default to 127.0.0.1 when no headers present", () => {
      const mockRequest = {
        headers: new Map(),
      } as any;

      mockRequest.headers.get = () => null;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("127.0.0.1");
    });

    it("should handle comma-separated x-forwarded-for list", () => {
      const mockRequest = {
        headers: new Map([
          ["x-forwarded-for", "203.0.113.1, 198.51.100.2, 192.0.2.3"],
        ]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("203.0.113.1");
    });

    it("should trim whitespace from extracted IP", () => {
      const mockRequest = {
        headers: new Map([["x-forwarded-for", "  203.0.113.50  "]]),
      } as any;

      const ip = getIPFromRequest(mockRequest);
      expect(ip).toBe("203.0.113.50");
    });
  });

  describe("getLocationFromIP", () => {
    it("should return default location for localhost", async () => {
      const location = await getLocationFromIP("127.0.0.1");

      expect(location).toEqual({
        country: "Local Development",
        countryCode: "XX",
        region: "N/A",
        city: "localhost",
        latitude: 0,
        longitude: 0,
        timezone: "UTC",
      });
    });

    it("should return default location for IPv6 localhost", async () => {
      const location = await getLocationFromIP("::1");

      expect(location).toEqual({
        country: "Local Development",
        countryCode: "XX",
        region: "N/A",
        city: "localhost",
        latitude: 0,
        longitude: 0,
        timezone: "UTC",
      });
    });

    it("should return default location for private IP 192.168.x.x", async () => {
      const location = await getLocationFromIP("192.168.1.100");

      expect(location).toEqual({
        country: "Local Development",
        countryCode: "XX",
        region: "N/A",
        city: "localhost",
        latitude: 0,
        longitude: 0,
        timezone: "UTC",
      });
    });

    it("should return default location for private IP 10.x.x.x", async () => {
      const location = await getLocationFromIP("10.0.0.1");

      expect(location).toEqual({
        country: "Local Development",
        countryCode: "XX",
        region: "N/A",
        city: "localhost",
        latitude: 0,
        longitude: 0,
        timezone: "UTC",
      });
    });

    it("should return default location for private IP 172.16-31.x.x", async () => {
      const location = await getLocationFromIP("172.16.0.1");
      expect(location?.country).toBe("Local Development");

      const location2 = await getLocationFromIP("172.31.255.255");
      expect(location2?.country).toBe("Local Development");
    });

    it("should return null for invalid IP addresses", async () => {
      const location = await getLocationFromIP("invalid-ip");
      expect(location).toBeNull();
    });

    it("should handle API fallback when geoip-lite fails", async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          country_name: "United Arab Emirates",
          country_code: "AE",
          region: "Dubai",
          city: "Dubai",
          latitude: 25.2048,
          longitude: 55.2708,
          timezone: "Asia/Dubai",
        }),
      };

      (global.fetch as jest.Mock).mockResolvedValueOnce(mockResponse);

      // Use a public IP that geoip-lite might not have
      const location = await getLocationFromIP("1.1.1.1");

      // Location should be returned (may have empty fields if geoip-lite returns partial data)
      expect(location).toBeDefined();
      if (location) {
        expect(typeof location.country).toBe("string");
        expect(typeof location.countryCode).toBe("string");
      }
    });

    it("should handle API errors gracefully", async () => {
      (global.fetch as jest.Mock).mockRejectedValueOnce(
        new Error("Network error"),
      );

      const location = await getLocationFromIP("8.8.8.8");
      // Should not throw, might return null or fallback data
      expect(location).toBeDefined();
    });

    it("should handle API timeout gracefully", async () => {
      const mockResponse = new Promise((resolve) => {
        setTimeout(() => resolve({ ok: false }), 10000);
      });

      (global.fetch as jest.Mock).mockReturnValueOnce(mockResponse);

      const location = await getLocationFromIP("8.8.8.8");
      // Should not hang, should timeout or return null
      expect(location).toBeDefined();
    });
  });

  describe("GCC Country Detection", () => {
    it("should recognize UAE country code", async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          country_name: "United Arab Emirates",
          country_code: "AE",
          region: "Dubai",
          city: "Dubai",
          latitude: 25.2048,
          longitude: 55.2708,
          timezone: "Asia/Dubai",
        }),
      };

      (global.fetch as jest.Mock).mockResolvedValueOnce(mockResponse);

      const location = await getLocationFromIP("203.0.113.1");

      if (location) {
        expect(location.countryCode).toBe("AE");
        expect(location.country).toContain("Emirates");
      }
    });

    it("should recognize Qatar country code", async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          country_name: "Qatar",
          country_code: "QA",
          region: "Doha",
          city: "Doha",
          latitude: 25.2854,
          longitude: 51.531,
          timezone: "Asia/Qatar",
        }),
      };

      (global.fetch as jest.Mock).mockResolvedValueOnce(mockResponse);

      const location = await getLocationFromIP("203.0.113.2");

      if (location) {
        expect(location.countryCode).toBe("QA");
        expect(location.country).toBe("Qatar");
      }
    });
  });

  describe("Edge Cases", () => {
    it("should handle empty IP string", async () => {
      const location = await getLocationFromIP("");
      expect(location).toBeDefined();
    });

    it("should handle IPv6 addresses", async () => {
      const location = await getLocationFromIP(
        "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
      );
      expect(location).toBeDefined();
    });

    it("should handle malformed API responses", async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          error: true,
          reason: "Invalid IP",
        }),
      };

      (global.fetch as jest.Mock).mockResolvedValueOnce(mockResponse);

      const location = await getLocationFromIP("999.999.999.999");
      expect(location).toBeNull();
    });
  });
});
