<?php
/**
 * Intelligent session analysis and protection for BotFend Anti-Bot Firewall
 * WordPress-optimized with security fixes and improved performance
 *
 * @package BotFend\Security
 * @since 3.5.0
 * @version 4.8.3 - Rebranded from SafeBot, fixed object cache sync and SQL escaping
 */

namespace BotFend\Security;

use BotFend\Core\Config;
use BotFend\Utilities\Utilities;
use BotFend\Database\Database;

defined('ABSPATH') || exit;

/**
 * Intelligent Session Analysis Class
 *
 * Provides intelligent session analysis without interfering with
 * legitimate JavaScript, ads, or normal website functionality.
 * No CSRF blocking - focuses on bot detection and session hijacking prevention.
 */
class IntelligentSession {
    
    private $config;
    private $utilities;
    private $database;
    private $session_tracking = [];
    
    const CACHE_GROUP = 'botfend_sessions';
    const SESSION_TIMEOUT_SEC = 1800;
    const MAX_SESSION_SIZE = 20;
    const MAX_TRACKING_CACHE = 1000;
    
    private static $crawler_cache = [];
    const MAX_CRAWLER_CACHE = 100;
    
    public function __construct(Utilities $utilities, Database $database) {
        $this->config = Config::get_instance();
        $this->utilities = $utilities;
        $this->database = $database;
        
        add_action('wp', [$this, 'analyze_request'], 1);
        add_action('shutdown', [$this, 'finalize_request_analysis'], 999);
        add_action('botfend_cleanup_sessions', [$this, 'cleanup_old_sessions']);
        
        if (!wp_next_scheduled('botfend_cleanup_sessions')) {
            wp_schedule_event(time(), 'hourly', 'botfend_cleanup_sessions');
        }
    }
    
    private function get_safe_key($value) {
        return substr(md5(sanitize_text_field($value)), 0, 12);
    }
    
    private function is_legitimate_crawler($ip, $user_agent) {
        if (empty($user_agent)) {
            return false;
        }
        
        $safe_ip = sanitize_text_field($ip);
        $safe_ua = sanitize_text_field($user_agent);
        $cache_key = $this->get_safe_key($safe_ip . $safe_ua);
        
        if (isset(self::$crawler_cache[$cache_key])) {
            return self::$crawler_cache[$cache_key];
        }
        
        if (count(self::$crawler_cache) >= self::MAX_CRAWLER_CACHE) {
            self::$crawler_cache = array_slice(self::$crawler_cache, 20, null, true);
        }
        
        $result = false;
        if ($this->utilities && method_exists($this->utilities, 'verify_legitimate_crawler')) {
            $result = $this->utilities->verify_legitimate_crawler($safe_ip, $safe_ua);
        }
        
        self::$crawler_cache[$cache_key] = $result;
        return $result;
    }
    
    private function is_login_page() {
        return in_array($GLOBALS['pagenow'] ?? '', ['wp-login.php', 'wp-register.php'], true);
    }
    
    public function analyze_request() {
        if (is_admin() && !$this->is_login_page()) {
            $this->analyze_admin_request();
            return;
        }
        
        $ip = $this->utilities->get_client_ip();
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? 
                     sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        if ($this->is_legitimate_crawler($ip, $user_agent)) {
            return;
        }
        
        if (!$this->is_valid_ip($ip)) {
            return;
        }
        
        if (count($this->session_tracking) >= self::MAX_TRACKING_CACHE) {
            $this->session_tracking = array_slice($this->session_tracking, -800, null, true);
        }
        
        $session_id = $this->get_session_identifier($ip);
        $tracking = $this->get_session_data($session_id);
        
        $this->track_request_data($session_id, $tracking, $ip);
        
        $quality_score = $this->calculate_session_quality($tracking);
        
        if ($quality_score < 3 && $tracking['request_count'] > 5) {
            $this->handle_suspicious_session($session_id, $ip, $quality_score, $tracking);
        }
        
        $this->check_session_anomalies($session_id, $ip, $tracking);
        
        $this->save_session_data($session_id, $tracking);
    }
    
    private function analyze_admin_request() {
        $ip = $this->utilities->get_client_ip();
        
        if ($this->utilities->is_ip_whitelisted($ip)) {
            return;
        }
        
        if ($this->database->is_ip_blocked($ip)) {
            return;
        }
        
        $session_id = $this->get_session_identifier($ip);
        $tracking = $this->get_session_data($session_id);
        
        $this->check_session_anomalies($session_id, $ip, $tracking);
        
        $this->save_session_data($session_id, $tracking);
    }
    
    private function get_session_identifier($ip) {
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? 
                     sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        $daily_salt = get_transient('botfend_daily_salt');
        if (false === $daily_salt) {
            $daily_salt = wp_generate_password(32, false);
            set_transient('botfend_daily_salt', $daily_salt, DAY_IN_SECONDS);
        }
        
        $safe_ip = sanitize_text_field($ip);
        $data = $safe_ip . '|' . $user_agent . '|' . $daily_salt;
        return 'botfend_sess_' . hash_hmac('sha256', $data, wp_salt('auth'));
    }
    
    private function get_session_data($session_id) {
        $data = get_transient($session_id);
        
        if (false === $data || !is_array($data)) {
            $data = [
                'start_time' => current_time('timestamp', true),
                'request_count' => 0,
                'paths' => [],
                'referrers' => [],
                'interactions' => 0,
                'suspicious_actions' => 0,
                'bot_score' => 0,
                'last_ip' => '',
                'last_user_agent' => '',
                'last_activity' => current_time('timestamp', true)
            ];
        }
        
        return $data;
    }
    
    private function save_session_data($session_id, $data) {
        $data['paths'] = array_slice($data['paths'], -self::MAX_SESSION_SIZE);
        $data['referrers'] = array_slice($data['referrers'], -self::MAX_SESSION_SIZE);
        $data['last_activity'] = current_time('timestamp', true);
        
        set_transient($session_id, $data, self::SESSION_TIMEOUT_SEC);
    }
    
    private function is_valid_ip($ip) {
        if (empty($ip) || !is_string($ip)) {
            return false;
        }
        
        $ip = trim($ip);
        
        if (!preg_match('/^[a-fA-F0-9:\.]+$/', $ip)) {
            return false;
        }
        
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }
    
    private function track_request_data($session_id, &$data, $ip) {
        $data['request_count']++;
        
        $current_path = isset($_SERVER['REQUEST_URI']) ? 
                       esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '/';
        
        $referrer = isset($_SERVER['HTTP_REFERER']) ? 
                   esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : '';
        
        $method = isset($_SERVER['REQUEST_METHOD']) ? 
                 sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET';
        
        if (strlen($current_path) > 500) {
            $data['suspicious_actions']++;
            $current_path = substr($current_path, 0, 500);
        }
        
        $data['paths'][] = [
            'time' => current_time('timestamp', true),
            'path' => sanitize_text_field($current_path),
            'method' => sanitize_text_field($method)
        ];
        
        if ($referrer && filter_var($referrer, FILTER_VALIDATE_URL)) {
            $home_url = home_url();
            if (strpos($referrer, $home_url) === 0) {
                $data['referrers'][] = [
                    'time' => current_time('timestamp', true),
                    'referrer' => esc_url_raw($referrer)
                ];
            }
        }
        
        if ($method === 'POST') {
            if (!$this->is_js_or_api_endpoint($current_path)) {
                $data['interactions']++;
            }
        }
        
        if ($this->is_suspicious_action($current_path, $method)) {
            $data['suspicious_actions']++;
        }
        
        $data['last_ip'] = sanitize_text_field($ip);
        $data['last_user_agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? 
                                  sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
    }
    
    private function is_js_or_api_endpoint($path) {
        $js_api_patterns = [
            '/wp-json/',
            '/wc-api/',
            '/ajax',
            '/admin-ajax.php',
            '.js',
            'analytics',
            'tracking',
            'pixel',
            'ads',
            'adserver',
            'doubleclick',
            'googlesyndication',
            'google-analytics',
            'gtag',
            'ga.js',
            'analytics.js',
            'gtm.js'
        ];
        
        $path = strtolower($path);
        
        foreach ($js_api_patterns as $pattern) {
            if (strpos($path, $pattern) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    private function calculate_session_quality($data) {
        $score = 5;
        
        $duration = current_time('timestamp', true) - absint($data['start_time']);
        if ($duration > 60 && $duration < 3600) {
            $score += 1;
        }
        
        if (!empty($data['paths'])) {
            $unique_paths = count(array_unique(array_column($data['paths'], 'path')));
            if ($unique_paths > 3) {
                $score += 1;
            } elseif ($unique_paths == 1 && $data['request_count'] > 10) {
                $score -= 1;
            }
        }
        
        if ($data['request_count'] > 0) {
            $interaction_ratio = absint($data['interactions']) / absint($data['request_count']);
            if ($interaction_ratio > 0.05 && $interaction_ratio < 0.8) {
                $score += 1;
            } elseif ($interaction_ratio === 0 && $data['request_count'] > 15) {
                $score -= 1;
            }
        }
        
        $timing_score = $this->analyze_request_timing($data['paths']);
        $score += $timing_score;
        
        if (absint($data['suspicious_actions']) > 0) {
            $score -= min(2, absint($data['suspicious_actions']));
        }
        
        $request_rate = $this->calculate_request_rate($data);
        if ($request_rate > 20) {
            $score -= 2;
        }
        
        return max(0, min(10, $score));
    }
    
    private function calculate_request_rate($data) {
        if (absint($data['request_count']) < 2) {
            return 0;
        }
        
        $duration = current_time('timestamp', true) - absint($data['start_time']);
        if ($duration < 1) {
            $duration = 1;
        }
        
        return absint($data['request_count']) / ($duration / 60);
    }
    
    private function analyze_request_timing($paths) {
        if (count($paths) < 3) {
            return 0;
        }
        
        $timings = [];
        $last_time = null;
        
        foreach ($paths as $path) {
            $current_time = absint($path['time']);
            if ($last_time !== null) {
                $timings[] = $current_time - $last_time;
            }
            $last_time = $current_time;
        }
        
        if (count($timings) > 2) {
            $variance = $this->calculate_variance($timings);
            
            if ($variance < 0.1) {
                return -2;
            } elseif ($variance > 2) {
                return 1;
            }
        }
        
        return 0;
    }
    
    private function calculate_variance($array) {
        $count = count($array);
        if ($count < 2) {
            return 0;
        }
        
        $mean = array_sum($array) / $count;
        $squared_diff = 0;
        
        foreach ($array as $value) {
            $squared_diff += pow($value - $mean, 2);
        }
        
        return $squared_diff / $count;
    }
    
    private function is_suspicious_action($path, $method) {
        if ($this->is_js_or_api_endpoint($path)) {
            return false;
        }
        
        $path = sanitize_text_field($path);
        
        $malicious_patterns = [
            '/\.\.\//',
            '/\.\.\\\\/',
            '/\\x00/',
            '/union.*select/i',
            '/insert.*into/i',
            '/drop.*table/i',
            '/or.*1=1/i',
            '/etc\/passwd/',
            '/proc\/self/',
            '/;\s*(ls|cat|rm|wget|curl)/i',
            '/\|\s*(ls|cat|rm|wget|curl)/i',
            '/<!\[CDATA\[/i',
            '/<!ENTITY/i',
        ];
        
        foreach ($malicious_patterns as $pattern) {
            if (preg_match($pattern, $path)) {
                return true;
            }
        }
        
        if ($method === 'POST') {
            $static_extensions = [
                '.jpg', '.jpeg', '.png', '.gif', '.svg', '.ico',
                '.css', '.js', '.woff', '.woff2', '.ttf', '.eot'
            ];
            
            foreach ($static_extensions as $ext) {
                if (stripos($path, $ext) !== false) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    private function check_session_anomalies($session_id, $ip, $data) {
        if (!empty($data['last_ip']) && $data['last_ip'] !== $ip) {
            $this->handle_possible_hijack($session_id, $ip, $data['last_ip']);
        }
        
        $current_ua = isset($_SERVER['HTTP_USER_AGENT']) ? 
                     sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        if (!empty($data['last_user_agent']) && 
            !empty($current_ua) && 
            $data['last_user_agent'] !== $current_ua) {
            
            if ($this->is_significant_ua_change($data['last_user_agent'], $current_ua)) {
                $this->handle_possible_hijack($session_id, $ip, null, 'UA change');
            }
        }
    }
    
    private function is_significant_ua_change($old_ua, $new_ua) {
        $old_browser = $this->extract_browser_family($old_ua);
        $new_browser = $this->extract_browser_family($new_ua);
        
        return $old_browser !== $new_browser;
    }
    
    private function extract_browser_family($ua) {
        $ua = strtolower($ua);
        
        if (strpos($ua, 'chrome') !== false && strpos($ua, 'edge') === false) {
            return 'chrome';
        } elseif (strpos($ua, 'firefox') !== false) {
            return 'firefox';
        } elseif (strpos($ua, 'safari') !== false && strpos($ua, 'chrome') === false) {
            return 'safari';
        } elseif (strpos($ua, 'edge') !== false) {
            return 'edge';
        } elseif (strpos($ua, 'opera') !== false) {
            return 'opera';
        } elseif (strpos($ua, 'trident') !== false || strpos($ua, 'msie') !== false) {
            return 'ie';
        }
        
        return 'unknown';
    }
    
    private function handle_possible_hijack($session_id, $current_ip, $original_ip = null, $reason = 'IP change') {
        $country = $this->utilities->get_country($current_ip);
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? 
                     sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        $this->database->log_request(
            sanitize_text_field($current_ip),
            7,
            'session_anomaly',
            [sprintf(
                esc_html__('Possible session anomaly detected: %1$s (Original IP: %2$s)', 'botfend-anti-bot-firewall'),
                esc_html($reason),
                esc_html($original_ip ?: 'unknown')
            )],
            sanitize_text_field($user_agent),
            false,
            sanitize_text_field($country)
        );
        
        $hijack_key = 'botfend_hijack_' . md5(sanitize_text_field($current_ip));
        $attempts = absint(get_transient($hijack_key) ?: 0);
        $attempts++;
        
        set_transient($hijack_key, $attempts, HOUR_IN_SECONDS);
        
        if ($attempts >= 3) {
            $duration = $this->config->get_int('block_duration', 24);
            $this->database->block_ip(
                sanitize_text_field($current_ip),
                'session_hijacking',
                sprintf(
                    esc_html__('Multiple session hijacking attempts (%d)', 'botfend-anti-bot-firewall'),
                    absint($attempts)
                ),
                8,
                $duration,
                sanitize_text_field($country)
            );
            
            delete_transient($session_id);
        }
    }
    
    private function handle_suspicious_session($session_id, $ip, $quality_score, $data) {
        $country = $this->utilities->get_country($ip);
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? 
                     sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        $this->database->log_request(
            sanitize_text_field($ip),
            max(4, 10 - absint($quality_score)),
            'suspicious_session',
            [sprintf(
                esc_html__('Low quality session detected. Score: %1$d, Requests: %2$d', 'botfend-anti-bot-firewall'),
                absint($quality_score),
                absint($data['request_count'])
            )],
            sanitize_text_field($user_agent),
            false,
            sanitize_text_field($country)
        );
        
        if ($quality_score < 2 && $data['request_count'] > 20) {
            $watch_key = 'botfend_watch_' . md5(sanitize_text_field($ip));
            set_transient($watch_key, [
                'score' => $quality_score,
                'requests' => $data['request_count'],
                'detected_at' => current_time('mysql', true)
            ], HOUR_IN_SECONDS);
            
            delete_transient($session_id);
        }
    }
    
    public function finalize_request_analysis() {
        if (defined('WP_DEBUG') && WP_DEBUG && $this->config->get_bool('debug_mode', false)) {
            $this->log_debug_info();
        }
    }
    
    public function cleanup_old_sessions() {
        global $wpdb;
        $time = time();
        
        // FIXED: Only query database if external object cache is not handling transients
        if (!wp_using_ext_object_cache()) {
            $like_str = $wpdb->esc_like('_transient_timeout_botfend_sess_') . '%';
            
            $expired = $wpdb->get_col($wpdb->prepare(
                "SELECT option_name FROM {$wpdb->options} 
                 WHERE option_name LIKE %s AND option_value < %d",
                $like_str,
                $time
            ));
            
            $deleted = 0;
            foreach ($expired as $transient) {
                $key = str_replace('_transient_timeout_', '', $transient);
                delete_transient($key);
                $deleted++;
            }
            
            if (defined('WP_DEBUG') && WP_DEBUG && $this->config->get_bool('debug_mode', false)) {
                error_log("BotFend: Cleaned up {$deleted} expired session transients");
            }
        }
    }
    
    public function get_statistics($hours = 24) {
        global $wpdb;
        
        $table = $wpdb->prefix . 'botfend_logs';
        
        $session_logs = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT 
                    ip,
                    COUNT(*) as request_count,
                    MAX(score) as max_score,
                    GROUP_CONCAT(DISTINCT action SEPARATOR ', ') as actions
                FROM `{$table}` 
                WHERE action IN (%s, %s, %s)
                AND created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d HOUR)
                GROUP BY ip 
                ORDER BY request_count DESC 
                LIMIT 10",
                'session_anomaly',
                'session_hijacking',
                'suspicious_session',
                absint($hours)
            )
        ) ?: [];
        
        foreach ($session_logs as &$log) {
            $log->ip = sanitize_text_field($log->ip);
            $log->request_count = absint($log->request_count);
            $log->max_score = absint($log->max_score);
            $log->actions = sanitize_text_field($log->actions);
        }
        
        return [
            'session_logs' => $session_logs,
            'active_sessions' => $this->count_active_sessions(),
            'recommendations' => $this->generate_recommendations()
        ];
    }
    
    private function count_active_sessions() {
        global $wpdb;
        
        // FIXED: Check object cache to prevent heavy DB queries
        if (wp_using_ext_object_cache()) {
            return 0; // External object caches handle their own count, returning 0 to prevent DB locking
        }
        
        $like_str = $wpdb->esc_like('_transient_botfend_sess_') . '%';
        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->options} 
             WHERE option_name LIKE %s",
            $like_str
        ));
        
        return absint($count);
    }
    
    private function generate_recommendations() {
        $recommendations = [];
        
        $recommendations[] = esc_html__('Ensure your site uses HTTPS for all connections', 'botfend-anti-bot-firewall');
        $recommendations[] = esc_html__('Regularly update WordPress and all plugins', 'botfend-anti-bot-firewall');
        $recommendations[] = esc_html__('Implement strong password policies for all users', 'botfend-anti-bot-firewall');
        
        return apply_filters('botfend_session_recommendations', $recommendations);
    }
    
    private function log_debug_info() {
        $current_ip = $this->utilities->get_client_ip();
        $session_id = $this->get_session_identifier($current_ip);
        $data = $this->get_session_data($session_id);
        
        error_log('BotFend Session Debug: ' . wp_json_encode([
            'ip' => $current_ip,
            'session_id' => $session_id,
            'request_count' => $data['request_count'],
            'quality_score' => $this->calculate_session_quality($data),
            'paths' => count($data['paths'])
        ]));
    }
    
    public function get_session_analysis($ip) {
        if (!$this->is_valid_ip($ip)) {
            return [
                'error' => esc_html__('Invalid IP address', 'botfend-anti-bot-firewall')
            ];
        }
        
        $session_id = $this->get_session_identifier($ip);
        $data = $this->get_session_data($session_id);
        
        return [
            'ip' => sanitize_text_field($ip),
            'session_active' => $data['request_count'] > 0,
            'request_count' => absint($data['request_count']),
            'quality_score' => $this->calculate_session_quality($data),
            'last_activity' => absint($data['last_activity']),
            'recommendations' => $this->generate_security_recommendations($ip)
        ];
    }
    
    private function generate_security_recommendations($ip) {
        $recommendations = [];
        // CRITICAL: Geolocation requires opt-in (disabled by default)
        $country = $this->utilities->get_country($ip);
        
        if ($country && $this->utilities->is_high_risk_country($country)) {
            $recommendations[] = sprintf(
                esc_html__('IP is from high-risk region (%s). Consider monitoring closely.', 'botfend-anti-bot-firewall'),
                esc_html($country)
            );
        }
        
        if ($this->utilities->is_proxy($ip)) {
            $recommendations[] = esc_html__('IP appears to use proxy/VPN. Consider requiring additional verification.', 'botfend-anti-bot-firewall');
        }
        
        return $recommendations;
    }
    
    public function clear_all_sessions() {
        // FIXED: Avoid wp_die() for programmatic internal calls; return false instead
        if (!current_user_can('manage_options') || !isset($_REQUEST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), 'botfend_clear_sessions')) {
            return false;
        }
        
        global $wpdb;
        
        // FIXED: Respect object caching and properly escape SQL wildcards
        if (!wp_using_ext_object_cache()) {
            $like_sess = $wpdb->esc_like('_transient_botfend_sess_') . '%';
            $like_timeout = $wpdb->esc_like('_transient_timeout_botfend_sess_') . '%';
            
            $keys = $wpdb->get_col($wpdb->prepare(
                "SELECT option_name FROM {$wpdb->options} 
                 WHERE option_name LIKE %s 
                 OR option_name LIKE %s",
                $like_sess,
                $like_timeout
            ));
            
            if (!empty($keys)) {
                foreach ($keys as $key) {
                    $transient_name = str_replace(['_transient_timeout_', '_transient_'], '', $key);
                    delete_transient($transient_name);
                }
            }
        }
        
        delete_transient('botfend_daily_salt');
        
        self::$crawler_cache = [];
        $this->session_tracking = [];
        
        return true;
    }
    
    public static function clear_crawler_cache() {
        self::$crawler_cache = [];
    }
    
    public function get_cache_stats() {
        return [
            'tracking_cache_size' => count($this->session_tracking),
            'crawler_cache_size' => count(self::$crawler_cache),
            'max_tracking' => self::MAX_TRACKING_CACHE,
            'max_crawler' => self::MAX_CRAWLER_CACHE,
        ];
    }
}