/**
 * User Currency Detection Service
 *
 * Detects user's preferred currency using a fallback chain:
 * 1. User Profile (preferred_currency field)
 * 2. IP Geolocation (ip-api.com)
 * 3. Browser Locale (Accept-Language header or navigator.language)
 * 4. Browser Timezone (Intl.DateTimeFormat)
 * 5. Default (QAR for GCC focus, fallback to GBP)
 *
 * @server-only - Uses fetch for IP geolocation
 */
import 'server-only';

import { cache } from '@/lib/cache';
import { logger } from '@/lib/logger';
import type { Currency } from '@/lib/types/pricing';
import {
  TIMEZONE_TO_COUNTRY,
  LOCALE_TO_COUNTRY,
  DEFAULT_CURRENCY,
  FALLBACK_CURRENCY,
  getCurrencyFromCountry,
  isSupportedCurrency,
} from '@/lib/config/currencies.config';

// Cache keys and TTLs
const IP_CACHE_KEY_PREFIX = 'ip_country:';
const IP_CACHE_TTL = 86400; // 24 hours (IPs don't change often)

// IP-API.com endpoint (free, no API key needed, 45 req/min limit)
const IP_API_URL = 'http://ip-api.com/json';

interface IPAPIResponse {
  status: 'success' | 'fail';
  country?: string;
  countryCode?: string;
  message?: string;
}

interface DetectionResult {
  currency: Currency;
  source: 'profile' | 'ip' | 'locale' | 'timezone' | 'default';
  countryCode?: string;
  confidence: 'high' | 'medium' | 'low';
}

/**
 * Detect country from IP address using ip-api.com
 * Caches results for 24 hours
 */
async function detectCountryFromIP(ipAddress: string): Promise<string | null> {
  // Skip for localhost/private IPs
  if (
    ipAddress === '127.0.0.1' ||
    ipAddress === '::1' ||
    ipAddress.startsWith('192.168.') ||
    ipAddress.startsWith('10.') ||
    ipAddress.startsWith('172.')
  ) {
    logger.debug('Skipping IP detection for private/localhost IP', { ipAddress });
    return null;
  }

  const cacheKey = `${IP_CACHE_KEY_PREFIX}${ipAddress}`;

  try {
    // Try cache first
    const cached = await cache.get<string>(cacheKey);
    if (cached) {
      logger.debug('IP country cache hit', { ipAddress, countryCode: cached });
      return cached;
    }

    // Fetch from API
    const response = await fetch(`${IP_API_URL}/${ipAddress}?fields=status,countryCode,message`, {
      signal: AbortSignal.timeout(3000), // 3 second timeout
    });

    if (!response.ok) {
      logger.warn('IP-API returned non-OK status', { status: response.status });
      return null;
    }

    const data = (await response.json()) as IPAPIResponse;

    if (data.status !== 'success' || !data.countryCode) {
      logger.warn('IP-API request failed', { message: data.message });
      return null;
    }

    // Cache the result
    await cache.set(cacheKey, data.countryCode, IP_CACHE_TTL);

    logger.info('IP geolocation successful', {
      ipAddress,
      countryCode: data.countryCode,
    });

    return data.countryCode;
  } catch (error) {
    logger.warn('IP geolocation failed', {
      ipAddress,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
    return null;
  }
}

/**
 * Parse Accept-Language header to get country code
 */
function parseAcceptLanguage(acceptLanguage: string | null): string | null {
  if (!acceptLanguage) return null;

  // Parse the header (e.g., "ar-QA,ar;q=0.9,en-US;q=0.8,en;q=0.7")
  const locales = acceptLanguage.split(',').map((part) => {
    const [locale] = part.trim().split(';');
    return locale;
  });

  // Try each locale in priority order
  for (const locale of locales) {
    const country = LOCALE_TO_COUNTRY[locale];
    if (country) {
      logger.debug('Detected country from Accept-Language', { locale, country });
      return country;
    }
  }

  return null;
}

/**
 * Parse timezone to get country code
 */
function parseTimezone(timezone: string | null): string | null {
  if (!timezone) return null;

  const country = TIMEZONE_TO_COUNTRY[timezone];
  if (country) {
    logger.debug('Detected country from timezone', { timezone, country });
    return country;
  }

  // Try partial matching for timezones not in our exact map
  for (const [tz, countryCode] of Object.entries(TIMEZONE_TO_COUNTRY)) {
    if (timezone.includes(tz.split('/')[1])) {
      logger.debug('Detected country from timezone (partial match)', {
        timezone,
        matched: tz,
        country: countryCode,
      });
      return countryCode;
    }
  }

  return null;
}

/**
 * Get user's preferred currency from their profile
 */
async function getProfileCurrency(userId: string | null): Promise<Currency | null> {
  if (!userId) return null;

  try {
    // Dynamic import to avoid circular dependency
    const { prisma } = await import('@/lib/db');

    const user = await prisma.users.findUnique({
      where: { id: userId },
      select: { preferredCurrency: true },
    });

    if (user?.preferredCurrency && isSupportedCurrency(user.preferredCurrency)) {
      logger.debug('Got currency from user profile', {
        userId,
        currency: user.preferredCurrency,
      });
      return user.preferredCurrency as Currency;
    }
  } catch (error) {
    logger.warn('Failed to get user profile currency', {
      userId,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
  }

  return null;
}

/**
 * Main function: Detect user's currency
 *
 * Fallback chain:
 * 1. User profile (if userId provided)
 * 2. IP geolocation (if ipAddress provided)
 * 3. Accept-Language header
 * 4. Browser timezone
 * 5. Default (QAR for GCC focus)
 */
export async function getUserCurrency(options: {
  userId?: string | null;
  ipAddress?: string | null;
  acceptLanguage?: string | null;
  timezone?: string | null;
}): Promise<DetectionResult> {
  const { userId, ipAddress, acceptLanguage, timezone } = options;

  // 1. Try user profile preference
  if (userId) {
    const profileCurrency = await getProfileCurrency(userId);
    if (profileCurrency) {
      return {
        currency: profileCurrency,
        source: 'profile',
        confidence: 'high',
      };
    }
  }

  // 2. Try IP geolocation
  if (ipAddress) {
    const countryCode = await detectCountryFromIP(ipAddress);
    if (countryCode) {
      const currency = getCurrencyFromCountry(countryCode);
      if (currency) {
        return {
          currency,
          source: 'ip',
          countryCode,
          confidence: 'high',
        };
      }
    }
  }

  // 3. Try Accept-Language header
  if (acceptLanguage) {
    const countryCode = parseAcceptLanguage(acceptLanguage);
    if (countryCode) {
      const currency = getCurrencyFromCountry(countryCode);
      if (currency) {
        return {
          currency,
          source: 'locale',
          countryCode,
          confidence: 'medium',
        };
      }
    }
  }

  // 4. Try timezone
  if (timezone) {
    const countryCode = parseTimezone(timezone);
    if (countryCode) {
      const currency = getCurrencyFromCountry(countryCode);
      if (currency) {
        return {
          currency,
          source: 'timezone',
          countryCode,
          confidence: 'medium',
        };
      }
    }
  }

  // 5. Default (QAR for GCC-focused business)
  logger.info('Using default currency - no detection succeeded', options);
  return {
    currency: DEFAULT_CURRENCY,
    source: 'default',
    confidence: 'low',
  };
}

/**
 * Extract client info from Next.js request headers
 */
export function extractClientInfo(request: Request): {
  ipAddress: string | null;
  acceptLanguage: string | null;
  timezone: string | null;
} {
  // Use request.headers directly (it's already a Headers-like object)
  const headers = request.headers;

  // Get IP address (check various headers for proxied requests)
  const ipAddress =
    headers.get('x-forwarded-for')?.split(',')[0].trim() ||
    headers.get('x-real-ip') ||
    headers.get('cf-connecting-ip') || // Cloudflare
    null;

  // Get Accept-Language header
  const acceptLanguage = headers.get('accept-language');

  // Get timezone (custom header, set by client)
  const timezone = headers.get('x-timezone');

  return { ipAddress, acceptLanguage, timezone };
}

/**
 * Helper: Get currency for a request (convenience function)
 */
export async function getCurrencyForRequest(
  request: Request,
  userId?: string | null
): Promise<DetectionResult> {
  const clientInfo = extractClientInfo(request);

  return getUserCurrency({
    userId,
    ipAddress: clientInfo.ipAddress,
    acceptLanguage: clientInfo.acceptLanguage,
    timezone: clientInfo.timezone,
  });
}

/**
 * Client-side: Detect currency from browser
 * This is a simplified version for client-side use
 */
export function detectBrowserCurrency(): Currency {
  if (typeof window === 'undefined') {
    return FALLBACK_CURRENCY;
  }

  try {
    // Try navigator.language
    const locale = navigator.language;
    if (locale) {
      const countryCode = LOCALE_TO_COUNTRY[locale];
      if (countryCode) {
        const currency = getCurrencyFromCountry(countryCode);
        if (currency) return currency;
      }
    }

    // Try timezone
    const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    if (timezone) {
      const countryCode = TIMEZONE_TO_COUNTRY[timezone];
      if (countryCode) {
        const currency = getCurrencyFromCountry(countryCode);
        if (currency) return currency;
      }
    }
  } catch (error) {
    // Note: This runs client-side, so we use console.warn since logger is server-only
    // eslint-disable-next-line no-console
    console.warn('Browser currency detection failed', error);
  }

  return DEFAULT_CURRENCY;
}
