<?php
/**
 * Utility Functions Class
 *
 * @package VMPFence
 * @since 1.0.0
 * @since 2.2.2 Added IP detection methods and trusted proxies support
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * VMPFence Utils Class
 *
 * @since 1.0.0
 */
class VMPFence_Utils {

    /**
     * Get real IP address
     *
     * @since 1.0.0
     * @since 2.2.2 Added support for user-defined detection methods and trusted proxies
     * @return string
     */
    public static function get_real_ip() {
        // Check if user has set a specific detection method
        $detection_method = VMPFence_Config::get('ip_detection_method', 'auto');
        
        if ($detection_method !== 'auto') {
            $ip = self::get_ip_by_method($detection_method);
            return $ip;
        }
        
        // Auto mode - try headers in priority order
        $ip_headers = array(
            'HTTP_CF_CONNECTING_IP',     // Cloudflare
            'HTTP_CLIENT_IP',            // Proxy
            'HTTP_X_FORWARDED_FOR',      // Load balancer/proxy
            'HTTP_X_REAL_IP',            // Nginx proxy
            'HTTP_X_FORWARDED',          // Proxy
            'HTTP_X_CLUSTER_CLIENT_IP',  // Cluster
            'HTTP_FORWARDED_FOR',        // Proxy
            'HTTP_FORWARDED',            // Proxy
            'REMOTE_ADDR'                // Standard
        );

        foreach ($ip_headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = sanitize_text_field(wp_unslash($_SERVER[$header]));
                
                // Handle comma-separated IPs
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                
                // Validate IP
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        $fallback_ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
        return $fallback_ip;
    }

    /**
     * Get IP address by specific detection method
     *
     * @since 2.2.2
     * @param string $method Detection method to use
     * @return string
     */
    public static function get_ip_by_method($method) {
        $ip = '';
        
        switch ($method) {
            case 'REMOTE_ADDR':
                // Use only REMOTE_ADDR (most secure)
                $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
                break;
                
            case 'HTTP_X_FORWARDED_FOR':
                // Use X-Forwarded-For header with trusted proxy filtering
                if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
                    $raw_ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR']));
                    
                    // Get trusted proxies (manual + preset)
                    require_once VMPFENCE_PLUGIN_DIR . 'all-options/class-proxy-presets.php';
                    $trusted_proxies = VMPFence_Proxy_Presets::get_unified_trusted_proxies();
                    
                    // Handle comma-separated IPs
                    if (strpos($raw_ip, ',') !== false) {
                        $ips = array_map('trim', explode(',', $raw_ip));
                        
                        // Filter out trusted proxies and get the first non-proxy IP
                        foreach ($ips as $candidate_ip) {
                            $is_trusted = false;
                            
                            foreach ($trusted_proxies as $proxy) {
                                if (VMPFence_Proxy_Presets::ip_in_range($candidate_ip, $proxy)) {
                                    $is_trusted = true;
                                    break;
                                }
                            }
                            
                            // Use first non-trusted IP
                            if (!$is_trusted && filter_var($candidate_ip, FILTER_VALIDATE_IP)) {
                                $ip = $candidate_ip;
                                break;
                            }
                        }
                        
                        // If all IPs are trusted, use the last one
                        if (empty($ip)) {
                            $ip = end($ips);
                        }
                    } else {
                        $ip = $raw_ip;
                    }
                }
                break;
                
            case 'HTTP_X_REAL_IP':
                // Use X-Real-IP header (common with Nginx)
                if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
                    $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_REAL_IP']));
                }
                break;
                
            case 'HTTP_CF_CONNECTING_IP':
                // Use Cloudflare CF-Connecting-IP header
                if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
                    $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP']));
                }
                break;
                
            case 'secure':
            case 'auto':
                // Auto/secure detection - try headers in priority order
                $ip_headers = array(
                    'HTTP_CF_CONNECTING_IP',
                    'HTTP_CLIENT_IP',
                    'HTTP_X_FORWARDED_FOR',
                    'HTTP_X_REAL_IP',
                    'HTTP_X_FORWARDED',
                    'HTTP_X_CLUSTER_CLIENT_IP',
                    'HTTP_FORWARDED_FOR',
                    'HTTP_FORWARDED',
                    'REMOTE_ADDR',
                );
                foreach ($ip_headers as $header) {
                    if (!empty($_SERVER[$header])) {
                        $candidate = sanitize_text_field(wp_unslash($_SERVER[$header]));
                        if (strpos($candidate, ',') !== false) {
                            $candidate = trim(explode(',', $candidate)[0]);
                        }
                        if (filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                            $ip = $candidate;
                            break;
                        }
                    }
                }
                break;

            default:
                // Unknown method - safe fallback to REMOTE_ADDR, no recursion
                $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
                break;
        }
        
        // Validate the IP
        if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP)) {
            return $ip;
        }
        
        // Fallback to REMOTE_ADDR if method fails
        $fallback = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
        return $fallback;
    }

    /**
     * Sanitize input data
     *
     * @since 1.0.0
     * @param mixed $input Input data
     * @param string $type Type of sanitization
     * @return mixed Sanitized data
     */
    public static function sanitize_input($input, $type = 'text') {
        switch ($type) {
            case 'email':
                return sanitize_email($input);
            
            case 'url':
                return esc_url_raw($input);
            
            case 'int':
                return intval($input);
            
            case 'float':
                return floatval($input);
            
            case 'bool':
                return (bool) $input;
            
            case 'array':
                return is_array($input) ? array_map(array(__CLASS__, 'sanitize_input'), $input) : array();
            
            case 'html':
                return wp_kses_post($input);
            
            case 'textarea':
                return sanitize_textarea_field($input);
            
            case 'key':
                return sanitize_key($input);
            
            case 'filename':
                return sanitize_file_name($input);
            
            case 'text':
            default:
                return sanitize_text_field($input);
        }
    }

    /**
     * Verify nonce
     *
     * @since 1.0.0
     * @param string $nonce Nonce value
     * @param string $action Nonce action
     * @return bool
     */
    public static function verify_nonce($nonce, $action = 'vmpfence_nonce') {
        return wp_verify_nonce($nonce, $action);
    }

    /**
     * Check user permissions
     *
     * @since 1.0.0
     * @param string $capability Capability to check
     * @return bool
     */
    public static function check_permissions($capability = 'vmpfence_view') {
        return current_user_can($capability);
    }

    /**
     * Validate IP address
     *
     * @since 1.0.0
     * @param string $ip IP address
     * @param bool $allow_private Allow private IPs
     * @return bool
     */
    public static function validate_ip($ip, $allow_private = true) {
        $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6;
        
        if (!$allow_private) {
            $flags |= FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
        }

        return filter_var($ip, FILTER_VALIDATE_IP, $flags) !== false;
    }

    /**
     * Encrypt data
     *
     * @since 1.0.0
     * @param string $data Data to encrypt
     * @param string $key Encryption key
     * @return string Encrypted data
     */
    public static function encrypt_data($data, $key = '') {
        if (empty($key)) {
            $key = self::get_encryption_key();
        }

        $iv = openssl_random_pseudo_bytes(16);
        $encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
        
        return base64_encode($iv . $encrypted);
    }

    /**
     * Decrypt data
     *
     * @since 1.0.0
     * @param string $data Encrypted data
     * @param string $key Encryption key
     * @return string Decrypted data
     */
    public static function decrypt_data($data, $key = '') {
        if (empty($key)) {
            $key = self::get_encryption_key();
        }

        $data = base64_decode($data);
        $iv = substr($data, 0, 16);
        $encrypted = substr($data, 16);
        
        return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
    }

    /**
     * Generate secure token
     *
     * @since 1.0.0
     * @param int $length Token length
     * @return string
     */
    public static function generate_secure_token($length = 32) {
        return bin2hex(random_bytes($length / 2));
    }

    /**
     * Check rate limiting
     *
     * @since 1.0.0
     * @param string $ip IP address
     * @param string $type Rate limit type
     * @return bool
     */
    public static function is_rate_limited($ip, $type = 'general') {
        global $wpdb;

        if (!VMPFence_Config::get('rate_limiting_enabled')) {
            return false;
        }

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_TRAFFIC_RATES);
        
        // Get rate limit settings
        $limits = self::get_rate_limits($type);
        $window_start = floor(time() / $limits['window']) * $limits['window'];

        // Get current count
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is properly escaped via VMPFence_Constants
        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT hitCount FROM $table_name WHERE IP = INET6_ATON(%s) AND window_start = %d",
            $ip,
            $window_start
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        $count = intval($count);

        // Update count
        $wpdb->replace(
            $table_name,
            array(
                'IP' => $ip,
                'hitCount' => $count + 1,
                'window_start' => $window_start,
                'blocked' => $count >= $limits['requests'] ? 1 : 0
            ),
            array('%s', '%d', '%d', '%d')
        );

        return $count >= $limits['requests'];
    }

    /**
     * Geolocate IP address
     *
     * @since 1.0.0
     * @param string $ip IP address
     * @return array
     */
    public static function geolocate_ip($ip) {
        if (!self::validate_ip($ip, false)) {
            return array(
                'country_code' => '',
                'country_name' => '',
                'region' => '',
                'city' => '',
                'latitude' => 0,
                'longitude' => 0
            );
        }

        // Check cache first
        $cache_key = 'vmpfence_geo_' . md5($ip);
        $cached = get_transient($cache_key);
        
        if ($cached !== false) {
            return $cached;
        }

        // Use multiple geolocation services
        $geo_data = self::get_geolocation_from_service($ip);

        // Cache result for 24 hours
        set_transient($cache_key, $geo_data, 86400);

        return $geo_data;
    }

    /**
     * Get file hash
     *
     * @since 1.0.0
     * @param string $file File path
     * @param string $algorithm Hash algorithm
     * @return string
     */
    public static function get_file_hash($file, $algorithm = 'md5') {
        if (!file_exists($file) || !is_readable($file)) {
            return '';
        }

        return hash_file($algorithm, $file);
    }

    /**
     * Get file size in human readable format
     *
     * @since 1.0.0
     * @param int $size Size in bytes
     * @return string
     */
    public static function format_file_size($size) {
        $units = array('B', 'KB', 'MB', 'GB', 'TB');
        
        for ($i = 0; $size > 1024 && $i < count($units) - 1; $i++) {
            $size /= 1024;
        }

        return round($size, 2) . ' ' . $units[$i];
    }

    /**
     * Get time difference in human readable format
     *
     * @since 1.0.0
     * @param int $timestamp Timestamp
     * @return string
     */
    public static function time_ago($timestamp) {
        $time_diff = time() - $timestamp;
        
        if ($time_diff < 60) {
            return __('Just now', 'vmpfence-security');
        } elseif ($time_diff < 3600) {
            $minutes = floor($time_diff / 60);
            /* translators: %d: number of minutes */
            return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'vmpfence-security'), $minutes);
        } elseif ($time_diff < 86400) {
            $hours = floor($time_diff / 3600);
            /* translators: %d: number of hours */
            return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'vmpfence-security'), $hours);
        } elseif ($time_diff < 2592000) {
            $days = floor($time_diff / 86400);
            /* translators: %d: number of days */
            return sprintf(_n('%d day ago', '%d days ago', $days, 'vmpfence-security'), $days);
        } else {
            return date_i18n(get_option('date_format'), $timestamp);
        }
    }

    /**
     * Check if string contains malicious patterns
     *
     * @since 1.0.0
     * @param string $string String to check
     * @return bool
     */
    public static function contains_malicious_patterns($string) {
        $patterns = array(
            // SQL injection patterns
            '/union.*select/i',
            '/select.*from/i',
            '/insert.*into/i',
            '/delete.*from/i',
            '/update.*set/i',
            '/drop.*table/i',
            
            // XSS patterns
            '/<script/i',
            '/javascript:/i',
            '/vbscript:/i',
            '/onload=/i',
            '/onclick=/i',
            '/onerror=/i',
            
            // File inclusion patterns
            '/\.\.[\/\\\\]/i',
            '/etc\/passwd/i',
            '/proc\/self/i',
            
            // Command injection patterns
            '/;.*cat/i',
            '/;.*ls/i',
            '/;.*pwd/i',
            '/;.*id/i'
        );

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $string)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Generate password hash
     *
     * @since 1.0.0
     * @param string $password Password
     * @return string
     */
    public static function hash_password($password) {
        return wp_hash_password($password);
    }

    /**
     * Verify password hash
     *
     * @since 1.0.0
     * @param string $password Password
     * @param string $hash Hash
     * @return bool
     */
    public static function verify_password($password, $hash) {
        return wp_check_password($password, $hash);
    }

    /**
     * Send email notification
     *
     * @since 1.0.0
     * @param string $to Recipient email
     * @param string $subject Email subject
     * @param string $message Email message
     * @param array $headers Email headers
     * @return bool
     */
    public static function send_email($to, $subject, $message, $headers = array()) {
        if (!VMPFence_Config::get('email_notifications_enabled')) {
            return false;
        }

        $default_headers = array(
            'Content-Type: text/html; charset=UTF-8',
            'From: ' . get_bloginfo('name') . ' <' . get_option('admin_email') . '>'
        );

        $headers = array_merge($default_headers, $headers);

        return wp_mail($to, $subject, $message, $headers);
    }

    /**
     * Log debug message
     *
     * @since 1.0.0
     * @param string $message Log message
     * @param string $level Log level
     * @return void
     */
    public static function log($message, $level = 'info') {
        if (!VMPFence_Config::get('enable_debug_logging')) {
            return;
        }

        $log_file = VMPFence_Constants::get_upload_dir(VMPFence_Constants::LOGS_DIR) . '/debug.log';
        
        // Create directory if it doesn't exist
        $log_dir = dirname($log_file);
        if (!file_exists($log_dir)) {
            wp_mkdir_p($log_dir);
        }

        $timestamp = gmdate('Y-m-d H:i:s');
        $log_entry = "[$timestamp] [$level] $message" . PHP_EOL;
        
        file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }

    /**
     * Get system information
     *
     * @since 1.0.0
     * @return array
     */
    public static function get_system_info() {
        global $wpdb;

        return array(
            'wordpress_version' => get_bloginfo('version'),
            'php_version' => PHP_VERSION,
            'mysql_version' => $wpdb->db_version(),
            'server_software' => isset($_SERVER['SERVER_SOFTWARE']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_SOFTWARE'])) : 'Unknown',
            'max_execution_time' => ini_get('max_execution_time'),
            'memory_limit' => ini_get('memory_limit'),
            'upload_max_filesize' => ini_get('upload_max_filesize'),
            'post_max_size' => ini_get('post_max_size'),
            'allow_url_fopen' => ini_get('allow_url_fopen') ? 'Yes' : 'No',
            'curl_version' => function_exists('curl_version') ? curl_version()['version'] : 'Not available',
            'openssl_version' => defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'Not available',
            'user_agent_string' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : 'Unknown',
            'remote_ip' => self::get_real_ip(),
            'server_ip' => isset($_SERVER['SERVER_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR'])) : 'Unknown',
            'multisite' => is_multisite() ? 'Yes' : 'No',
            'active_plugins' => get_option('active_plugins', array()),
            'active_theme' => get_option('current_theme')
        );
    }

    /**
     * Get encryption key
     *
     * @since 1.0.0
     * @return string
     */
    private static function get_encryption_key() {
        $key = get_option('vmpfence_encryption_key');
        
        if (!$key) {
            $key = self::generate_secure_token(64);
            update_option('vmpfence_encryption_key', $key);
        }

        return $key;
    }

    /**
     * Get rate limits by type
     *
     * @since 1.0.0
     * @param string $type Rate limit type
     * @return array
     */
    private static function get_rate_limits($type) {
        $limits = array(
            'general' => array(
                'requests' => VMPFence_Config::get('rate_limit_requests', 30),
                'window' => VMPFence_Config::get('rate_limit_window', 300)
            ),
            'login' => array(
                'requests' => 5,
                'window' => 300
            ),
            '404' => array(
                'requests' => VMPFence_Config::get('rate_limit_404_requests', 20),
                'window' => 300
            ),
            'xmlrpc' => array(
                'requests' => 10,
                'window' => 300
            )
        );

        return $limits[$type] ?? $limits['general'];
    }

    /**
     * Get geolocation from service
     *
     * @since 1.0.0
     * @param string $ip IP address
     * @return array
     */
    private static function get_geolocation_from_service($ip) {
        // Default response
        $default = array(
            'country_code' => '',
            'country_name' => '',
            'region' => '',
            'city' => '',
            'latitude' => 0,
            'longitude' => 0
        );

        // Try ip-api.com (free service)
        $response = wp_remote_get("http://ip-api.com/json/$ip", array(
            'timeout' => 5,
            'user-agent' => 'VMPFence/' . VMPFENCE_VERSION
        ));

        if (is_wp_error($response)) {
            return $default;
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if (!$data || $data['status'] !== 'success') {
            return $default;
        }

        return array(
            'country_code' => $data['countryCode'] ?? '',
            'country_name' => $data['country'] ?? '',
            'region' => $data['regionName'] ?? '',
            'city' => $data['city'] ?? '',
            'latitude' => $data['lat'] ?? 0,
            'longitude' => $data['lon'] ?? 0
        );
    }

    /**
     * Check if user agent is suspicious
     *
     * @since 1.0.0
     * @param string $user_agent User agent string
     * @return bool
     */
    public static function is_suspicious_user_agent($user_agent) {
        $suspicious_patterns = array(
            'sqlmap',
            'nikto',
            'nmap',
            'masscan',
            'zmap',
            'acunetix',
            'nessus',
            'openvas',
            'w3af',
            'burp',
            'owasp'
        );

        $user_agent = strtolower($user_agent);

        foreach ($suspicious_patterns as $pattern) {
            if (strpos($user_agent, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Clean temporary files
     *
     * @since 1.0.0
     * @return int Number of files cleaned
     */
    public static function clean_temp_files() {
        $temp_dir = VMPFence_Constants::get_upload_dir('temp');
        
        if (!is_dir($temp_dir)) {
            return 0;
        }

        $files = glob($temp_dir . '/*');
        $cleaned = 0;
        $max_age = 86400; // 24 hours

        foreach ($files as $file) {
            if (is_file($file) && (time() - filemtime($file)) > $max_age) {
                if (unlink($file)) {
                    $cleaned++;
                }
            }
        }

        return $cleaned;
    }

    /**
     * Get memory usage
     *
     * @since 1.0.0
     * @return array
     */
    public static function get_memory_usage() {
        return array(
            'current' => memory_get_usage(true),
            'peak' => memory_get_peak_usage(true),
            'limit' => self::parse_memory_limit(ini_get('memory_limit')),
            'current_formatted' => self::format_file_size(memory_get_usage(true)),
            'peak_formatted' => self::format_file_size(memory_get_peak_usage(true)),
            'limit_formatted' => self::format_file_size(self::parse_memory_limit(ini_get('memory_limit')))
        );
    }

    /**
     * Parse memory limit string
     *
     * @since 1.0.0
     * @param string $limit Memory limit string
     * @return int Memory limit in bytes
     */
    private static function parse_memory_limit($limit) {
        $limit = trim($limit);
        $last = strtolower($limit[strlen($limit) - 1]);
        $limit = (int) $limit;

        switch ($last) {
            case 'g':
                $limit *= 1024;
            case 'm':
                $limit *= 1024;
            case 'k':
                $limit *= 1024;
        }

        return $limit;
    }
    
    public static function get_country_code($ip = null) {
        return null;
    }
    
    public static function log_event($type, $data) {
        global $wpdb;
        $table = $wpdb->prefix . 'vmpfence_waf_log';
        $ip = $data['ip'] ?? self::get_real_ip();
        
        $wpdb->query($wpdb->prepare(
            "INSERT INTO $table (attackLogTime, IP, countryCode, severity, action, actionDescription, actionData, logMsg, request_uri, http_referer, http_user_agent) 
            VALUES (%f, INET6_ATON(%s), %s, %d, %s, %s, %s, %s, %s, %s, %s)",
            microtime(true),
            $ip,
            $data['country_code'] ?? '',
            $data['severity'] ?? 50,
            $data['action'] ?? 'blocked',
            $data['description'] ?? $type,
            isset($data['action_data']) ? maybe_serialize($data['action_data']) : '',
            $data['log_msg'] ?? '',
            $data['request_uri'] ?? ($_SERVER['REQUEST_URI'] ?? ''),
            $data['http_referer'] ?? ($_SERVER['HTTP_REFERER'] ?? ''),
            $data['http_user_agent'] ?? ($_SERVER['HTTP_USER_AGENT'] ?? '')
        ));
    }
}
