<?php
/**
 * Country Blocker - Real-time Enforcement
 * 
 * Handles country-based blocking with GeoIP lookup and caching
 *
 * @package VMPFence
 * @since 2.2.0
 */

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

/**
 * Country Blocking Enforcement Class
 * 
 * Called by VMPFence_Firewall_Bootstrap during request validation
 */
class VMPFence_Country_Blocker {

    /**
     * Check if current visitor should be blocked by country
     * 
     * @param string $ip Visitor IP address (provided by firewall bootstrap)
     * @throws VMPFence_Block_Exception If visitor should be blocked
     * @return void
     */
    public static function check_visitor($ip = null) {
        // Get IP if not provided
        if (!$ip) {
            $ip = self::get_visitor_ip();
        }
        
        // Check if feature is enabled
        if (!self::is_feature_enabled()) {
            return;
        }

        // Validate IP
        if (!$ip || $ip === '127.0.0.1' || $ip === '::1') {
            return; // Skip localhost
        }

        // Check bypass mechanisms first (before country check)
        if (self::check_bypass()) {
            return;
        }

        // Get country code (with caching)
        $country_code = self::get_country_code($ip);
        if (!$country_code) {
            return; // Couldn't determine country
        }
        
        // Validate country code format (must be 2 uppercase letters)
        if (!preg_match('/^[A-Z]{2}$/', $country_code)) {
            if (class_exists('VMPFence_Secure_Logger')) {
                VMPFence_Secure_Logger::log('warning', 'Invalid country code format from GeoIP', [
                    'ip' => $ip,
                    'country_code' => $country_code
                ]);
            }
            return;
        }

        // Check if country is blocked
        if (!class_exists('VMPFence_Blocked_Country')) {
            return;
        }

        $blocked = VMPFence_Blocked_Country::is_country_blocked($country_code);
        if (!$blocked) {
            return; // Not blocked
        }

        // Determine blocking type
        $block_site = isset($blocked['block_site']) && $blocked['block_site'] == 1;
        $block_login = isset($blocked['block_login']) && $blocked['block_login'] == 1;

        // Check if we should block this request
        $should_block = false;
        
        if ($block_site) {
            // Block entire site access
            $should_block = true;
        } elseif ($block_login && self::is_login_request()) {
            // Block only login attempts (when site access is allowed)
            $should_block = true;
        }
        
        // Edge case: If neither block_site nor block_login is set, don't block
        // This handles corrupted database entries
        if (!$block_site && !$block_login) {
            return;
        }

        if (!$should_block) {
            return;
        }

        // Increment block counter
        VMPFence_Blocked_Country::increment_block_count($country_code);

        // Log the block
        self::log_block($ip, $country_code, $blocked);

        // Get country name for exception message
        $country_name = isset($blocked['country_name']) ? $blocked['country_name'] : $country_code;
        $block_reason = $block_site ? 'blocked_country_site' : 'blocked_country_login';
        
        // Throw block exception (firewall bootstrap will handle display)
        $exception = new VMPFence_Block_Exception(
            "Access denied: Visitors from {$country_name} are not permitted"
        );
        
        // Use setter methods for protected properties
        $exception->set_block_type($block_reason);
        
        // Store country data in block_data array
        $exception->set_block_data(array_merge($blocked, [
            'country_code' => $country_code,
            'country_name' => $country_name
        ]));
        
        // Add country properties for bootstrap detection
        $exception->country_code = $country_code;
        $exception->country_name = $country_name;
        
        throw $exception;
    }

    /**
     * Check if feature is enabled
     */
    private static function is_feature_enabled() {
        // Premium license check (country blocking is premium only)
        if (!class_exists('VMPFence_License')) {
            return false;
        }
        
        $license_type = VMPFence_License::get_license_type();
        
        // Check for premium license (with fallback for missing constants)
        $is_premium = false;
        if (class_exists('VMPFence_Constants')) {
            $is_premium = ($license_type === VMPFence_Constants::LICENSE_PREMIUM);
        } else {
            // Fallback: string comparison
            $is_premium = ($license_type === 'premium' || $license_type === 'Premium');
        }
        
        if (!$is_premium) {
            return false;
        }
        
        // Check if any countries are actually blocked
        // If no countries blocked, no need to run checks
        if (!class_exists('VMPFence_Blocked_Country')) {
            return false;
        }
        
        // Simple check: at least one active country exists
        // Use a cached transient to avoid DB query on every request
        $has_blocked_countries = get_transient('vmpfence_has_blocked_countries');
        
        if ($has_blocked_countries === false) {
            // Cache miss - query database
            global $wpdb;
            $table = $wpdb->prefix . 'vmpfence_blocked_countries';
            
            // Check if table exists first
            $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table));
            
            if ($table_exists !== $table) {
                return false;
            }
            
            // Query blocked countries count (no prepare needed for static query with esc_sql)
            $count = $wpdb->get_var("SELECT COUNT(*) FROM " . esc_sql($table) . " WHERE is_active = 1");
            $has_blocked_countries = ($count > 0) ? 'yes' : 'no';
            
            // Cache for 5 minutes
            set_transient('vmpfence_has_blocked_countries', $has_blocked_countries, 5 * MINUTE_IN_SECONDS);
        }
        
        return ($has_blocked_countries === 'yes');
    }

    /**
     * Get visitor IP address
     */
    private static function get_visitor_ip() {
        $ip = '';

        // Check various headers for proxy/CDN scenarios
        $headers = [
            'HTTP_CF_CONNECTING_IP', // Cloudflare
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];

        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = $_SERVER[$header];
                
                // Handle comma-separated IPs (X-Forwarded-For)
                if (strpos($ip, ',') !== false) {
                    $ips = explode(',', $ip);
                    $ip = trim($ips[0]);
                }
                
                // Validate IP (both IPv4 and IPv6, exclude private/reserved ranges for public blocking)
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    break;
                }
            }
        }

        // If no valid public IP found, return empty (will be caught by localhost check)
        return $ip;
    }

    /**
     * Get country code from IP with caching
     */
    private static function get_country_code($ip) {
        // Use local GeoIP database lookup (standalone, no API dependency)
        if (!class_exists('VMPFence_GeoIP_Lookup')) {
            return false;
        }

        try {
            // Local database lookup (<5ms with caching)
            $country_code = VMPFence_GeoIP_Lookup::lookup($ip);

            if ($country_code) {
                return $country_code;
            }
        } catch (Exception $e) {
            if (class_exists('VMPFence_Secure_Logger')) {
                VMPFence_Secure_Logger::log('error', 'GeoIP lookup failed', [
                    'ip' => $ip,
                    'error' => $e->getMessage()
                ]);
            }
        }

        return false;
    }

    /**
     * Check bypass mechanisms
     * @return bool True if visitor should bypass country blocking
     */
    private static function check_bypass() {
        // 1. Check if user is logged in admin (not just any user)
        if (is_user_logged_in() && current_user_can('manage_options')) {
            return true;
        }
        
        // 2. Check if logged-in users should bypass (new feature)
        // Default is 0 (allow logged-in users to bypass)
        if (is_user_logged_in() && class_exists('VMPFence_Config')) {
            $block_logged_in = VMPFence_Config::get('country_block_logged_in', 0);
            if ($block_logged_in == 0) {
                // Setting is disabled, allow logged-in users to bypass
                return true;
            }
        }

        // 3. Check bypass cookie
        $bypass_cookie = get_option('vmpfence_country_bypass_cookie', '');
        if (!empty($bypass_cookie) && isset($_COOKIE['vmpfence_bypass']) && $_COOKIE['vmpfence_bypass'] === $bypass_cookie) {
            return true;
        }

        // 4. Check bypass URL parameter
        $bypass_field = get_option('vmpfence_country_bypass_field', '');
        $bypass_value = get_option('vmpfence_country_bypass_value', '');
        
        if (!empty($bypass_field) && !empty($bypass_value)) {
            // Validate GET parameter exists and is not empty
            if (isset($_GET[$bypass_field]) && !empty($_GET[$bypass_field])) {
                // Sanitize GET parameter for security
                $get_value = sanitize_text_field($_GET[$bypass_field]);
                
                // Use timing-safe comparison to prevent timing attacks
                if (hash_equals($bypass_value, $get_value)) {
                    // Determine cookie value: use stored cookie value, or generate from bypass_value
                    $cookie_value = !empty($bypass_cookie) ? $bypass_cookie : hash('sha256', $bypass_value . AUTH_SALT);
                    $secure = is_ssl();
                    $domain = parse_url(home_url(), PHP_URL_HOST);
                    
                    // Only set cookie if headers not already sent
                    if (!headers_sent()) {
                        // PHP 7.3+ supports array options for setcookie
                        // Plugin requires PHP 7.4+, so this is safe
                        if (PHP_VERSION_ID >= 70300) {
                            setcookie(
                                'vmpfence_bypass',
                                $cookie_value,
                                array(
                                    'expires' => time() + (30 * 24 * 3600),
                                    'path' => '/',
                                    'domain' => $domain ?: '',
                                    'secure' => $secure,
                                    'httponly' => true,
                                    'samesite' => 'Strict'
                                )
                            );
                        } else {
                            // Fallback for PHP < 7.3 (shouldn't happen, but defensive programming)
                            setcookie('vmpfence_bypass', $cookie_value, time() + (30 * 24 * 3600), '/', $domain ?: '', $secure, true);
                        }
                        
                        // Save generated cookie value to database if it was empty
                        if (empty($bypass_cookie)) {
                            update_option('vmpfence_country_bypass_cookie', $cookie_value);
                        }
                    }
                    return true;
                }
            }
        }

        // 5. Check IP allowlist
        if (class_exists('VMPFence_Config')) {
            $allowlisted_ips = VMPFence_Config::get('ip_allowlist', []);
            $visitor_ip = self::get_visitor_ip();
            
            if (in_array($visitor_ip, $allowlisted_ips)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if current request is a login attempt
     */
    private static function is_login_request() {
        // Don't block admin-ajax.php even if login action present
        if (isset($_SERVER['SCRIPT_NAME']) && strpos($_SERVER['SCRIPT_NAME'], 'admin-ajax.php') !== false) {
            return false;
        }
        
        // Check if accessing wp-login.php
        if (isset($_SERVER['SCRIPT_NAME']) && strpos($_SERVER['SCRIPT_NAME'], 'wp-login.php') !== false) {
            return true;
        }

        // Check if POST to wp-login.php
        if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'wp-login.php') !== false) {
            return true;
        }

        // Check for login action
        if (isset($_REQUEST['action']) && in_array($_REQUEST['action'], ['login', 'lostpassword', 'register'])) {
            return true;
        }

        return false;
    }

    /**
     * Log the country block
     */
    private static function log_block($ip, $country_code, $blocked_data) {
        // Determine block type
        $block_type = (isset($blocked_data['block_site']) && $blocked_data['block_site'] == 1) ? 'site' : 'login';
        
        // Log to dedicated country block logs table
        if (class_exists('VMPFence_Blocked_Country')) {
            VMPFence_Blocked_Country::log_block_event([
                'ip' => $ip,
                'country_code' => $country_code,
                'country_name' => isset($blocked_data['country_name']) ? $blocked_data['country_name'] : '',
                'block_type' => $block_type,
                'request_uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
                'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''
            ]);
        }

        // Log to secure logger (if available)
        if (class_exists('VMPFence_Secure_Logger')) {
            VMPFence_Secure_Logger::log('info', 'Country block enforced', [
                'ip' => $ip,
                'country_code' => $country_code,
                'country_name' => $blocked_data['country_name'] ?? 'Unknown',
                'block_type' => $block_type,
                'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''
            ]);
        }

        // Log to API via Logging Service (if available)
        if (class_exists('VMPFence_Logging_Service')) {
            try {
                $logging_service = VMPFence_Logging_Service::get_instance();
                $logging_service->log_event('country_blocked', 'warning', [
                    'country_code' => $country_code,
                    'country_name' => $blocked_data['country_name'] ?? 'Unknown',
                    'ip' => $ip,
                    'block_type' => $block_type,
                    'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
                    'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''
                ], 'firewall');
            } catch (Exception $e) {
                // Silently fail - don't break the block
            }
        }
    }
}
