<?php
/**
 * GEO-IP Detection Service
 * 
 * Detects visitor country to apply correct regional compliance
 */

if (!defined('ABSPATH'))
    exit;

class Biscotti_GeoIP
{

    /**
     * Cached country code
     */
    private static $cached_country = null;

    /**
     * Free GeoIP API endpoints (fallback chain)
     */
    private static $api_endpoints = [
        'ipapi' => 'https://ipapi.co/%s/country/',
        'ipinfo' => 'https://ipinfo.io/%s/country',
        'geoplugin' => 'https://www.geoplugin.net/json.gp?ip=%s',
    ];

    /**
     * Get visitor country code
     */
    public static function get_country()
    {
        // Check cache first
        if (self::$cached_country !== null) {
            return self::$cached_country;
        }

        // Check transient cache
        $ip = self::get_visitor_ip();
        $cache_key = 'biscotti_geo_' . md5($ip);
        $cached = get_transient($cache_key);

        if ($cached !== false) {
            self::$cached_country = $cached;
            return $cached;
        }

        // Try to detect from headers (CloudFlare, etc.)
        $country = self::detect_from_headers();

        if (!$country) {
            // Try API lookup
            $country = self::lookup_country($ip);
        }

        // Default to unknown
        if (!$country) {
            $country = 'XX';
        }

        // Cache for 24 hours
        set_transient($cache_key, $country, DAY_IN_SECONDS);
        self::$cached_country = $country;

        return $country;
    }

    /**
     * Get visitor IP address
     * 
     * Priority: CDN headers (trusted) > REMOTE_ADDR (always available).
     * We intentionally do NOT trust HTTP_X_FORWARDED_FOR or HTTP_CLIENT_IP
     * as these are trivially spoofable by the client and could be used
     * to bypass geo-restrictions or poison the GeoIP cache.
     */
    public static function get_visitor_ip()
    {
        // Trusted CDN/proxy headers (set by the infrastructure, not the client)
        $trusted_headers = [
            'HTTP_CF_CONNECTING_IP',     // CloudFlare (trusted — set by CF edge)
            'HTTP_X_REAL_IP',            // Nginx reverse proxy (trusted — set by server)
        ];

        foreach ($trusted_headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = sanitize_text_field(wp_unslash($_SERVER[$header]));

                // Handle comma-separated list
                if (strpos($ip, ',') !== false) {
                    $ips = explode(',', $ip);
                    $ip = trim($ips[0]);
                }

                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        // Fallback: REMOTE_ADDR (always set by the web server)
        $remote_addr = isset($_SERVER['REMOTE_ADDR'])
            ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))
            : '127.0.0.1';

        if (!filter_var($remote_addr, FILTER_VALIDATE_IP)) {
            $remote_addr = '127.0.0.1';
        }

        return $remote_addr;
    }

    /**
     * Detect country from headers (CDN/proxy headers)
     */
    private static function detect_from_headers()
    {
        $country_headers = [
            'HTTP_CF_IPCOUNTRY',          // CloudFlare
            'HTTP_X_COUNTRY_CODE',        // Some proxies
            'HTTP_X_GEO_COUNTRY',         // Custom
            'GEOIP_COUNTRY_CODE',         // Apache mod_geoip
        ];

        foreach ($country_headers as $header) {
            if (!empty($_SERVER[$header])) {
                // Sanitize $_SERVER input per WordPress security guidelines
                $raw_value = sanitize_text_field(wp_unslash($_SERVER[$header]));

                // Validate: must be exactly 2 uppercase letters (A-Z only)
                if (strlen($raw_value) === 2 && preg_match('/^[A-Z]{2}$/i', $raw_value)) {
                    return strtoupper($raw_value);
                }
            }
        }

        return null;
    }

    /**
     * Lookup country via API
     */
    private static function lookup_country($ip)
    {
        // Skip for local IPs
        if (in_array($ip, ['127.0.0.1', '::1']) || strpos($ip, '192.168.') === 0) {
            return 'XX';
        }

        // Try ipapi.co first (simple, returns just country code)
        $response = wp_remote_get(
            sprintf(self::$api_endpoints['ipapi'], $ip),
            ['timeout' => 3, 'sslverify' => true]
        );

        if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
            $country = trim(wp_remote_retrieve_body($response));
            if (strlen($country) === 2 && ctype_alpha($country)) {
                return strtoupper($country);
            }
        }

        // Fallback to geoplugin (returns JSON)
        $response = wp_remote_get(
            sprintf(self::$api_endpoints['geoplugin'], $ip),
            ['timeout' => 3, 'sslverify' => true]
        );

        if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
            $data = json_decode(wp_remote_retrieve_body($response), true);
            if (isset($data['geoplugin_countryCode']) && strlen($data['geoplugin_countryCode']) === 2) {
                return strtoupper($data['geoplugin_countryCode']);
            }
        }

        return null;
    }

    /**
     * Get region code for visitor
     */
    public static function get_visitor_region()
    {
        $country = self::get_country();
        return Biscotti_Regions::get_region_by_country($country);
    }

    /**
     * Check if visitor is in EU
     */
    public static function is_eu()
    {
        $region = self::get_visitor_region();
        return $region === 'eu';
    }

    /**
     * Check if visitor is in US (California specifically matters for CCPA)
     */
    public static function is_us()
    {
        return self::get_country() === 'US';
    }

    /**
     * Get detailed location (for US state-level compliance)
     */
    public static function get_us_state()
    {
        $ip = self::get_visitor_ip();
        $cache_key = 'biscotti_geo_state_' . md5($ip);
        $cached = get_transient($cache_key);

        if ($cached !== false) {
            return $cached;
        }

        // Query ipinfo for region
        $response = wp_remote_get(
            sprintf('https://ipinfo.io/%s/region', $ip),
            ['timeout' => 3, 'sslverify' => true]
        );

        $state = 'XX';

        if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
            $region = trim(wp_remote_retrieve_body($response));
            // Map state name to code
            $state = self::state_name_to_code($region);
        }

        set_transient($cache_key, $state, DAY_IN_SECONDS);
        return $state;
    }

    /**
     * Map US state name to its 2-letter code.
     * Full 50 states + DC so GeoIP can resolve any US visitor to the correct
     * state-level jurisdiction (US_XX); the active set of state privacy laws is
     * governed server-side by jurisdiction_configs, not by this lookup.
     */
    private static function state_name_to_code($name)
    {
        $states = [
            'Alabama' => 'AL', 'Alaska' => 'AK', 'Arizona' => 'AZ', 'Arkansas' => 'AR',
            'California' => 'CA', 'Colorado' => 'CO', 'Connecticut' => 'CT', 'Delaware' => 'DE',
            'Florida' => 'FL', 'Georgia' => 'GA', 'Hawaii' => 'HI', 'Idaho' => 'ID',
            'Illinois' => 'IL', 'Indiana' => 'IN', 'Iowa' => 'IA', 'Kansas' => 'KS',
            'Kentucky' => 'KY', 'Louisiana' => 'LA', 'Maine' => 'ME', 'Maryland' => 'MD',
            'Massachusetts' => 'MA', 'Michigan' => 'MI', 'Minnesota' => 'MN', 'Mississippi' => 'MS',
            'Missouri' => 'MO', 'Montana' => 'MT', 'Nebraska' => 'NE', 'Nevada' => 'NV',
            'New Hampshire' => 'NH', 'New Jersey' => 'NJ', 'New Mexico' => 'NM', 'New York' => 'NY',
            'North Carolina' => 'NC', 'North Dakota' => 'ND', 'Ohio' => 'OH', 'Oklahoma' => 'OK',
            'Oregon' => 'OR', 'Pennsylvania' => 'PA', 'Rhode Island' => 'RI', 'South Carolina' => 'SC',
            'South Dakota' => 'SD', 'Tennessee' => 'TN', 'Texas' => 'TX', 'Utah' => 'UT',
            'Vermont' => 'VT', 'Virginia' => 'VA', 'Washington' => 'WA', 'West Virginia' => 'WV',
            'Wisconsin' => 'WI', 'Wyoming' => 'WY', 'District of Columbia' => 'DC',
        ];

        return isset($states[$name]) ? $states[$name] : 'XX';
    }

    /**
     * Check if CCPA applies (California residents)
     */
    public static function ccpa_applies()
    {
        if (!self::is_us()) {
            return false;
        }

        return self::get_us_state() === 'CA';
    }
}
