<?php
/**
 * Advanced Antibot Detection System
 * Independent server-side bot detection without external services
 */

class AdvancedAntibotSystem {
    
    /**
     * Perform comprehensive bot detection
     * @param array $request_data Request information
     * @return array Detection result with score and reasons
     */
    public static function detectBot($request_data) {
        $score = 0;
        $max_score = 100;
        $reasons = [];
        $checks_passed = [];
        
        // Extract data
        $ip = $request_data['ip'] ?? '';
        $user_agent = $request_data['user_agent'] ?? '';
        $referer = $request_data['referer'] ?? '';
        $headers = $request_data['headers'] ?? [];
        $fingerprint = $request_data['fingerprint'] ?? [];
        
        // ============================================
        // 1. HTTP HEADERS ANALYSIS (20 points)
        // ============================================
        $header_score = self::analyzeHeaders($headers);
        $score += $header_score['score'];
        if ($header_score['score'] > 0) {
            $reasons = array_merge($reasons, $header_score['reasons']);
        } else {
            $checks_passed[] = 'headers_valid';
        }
        
        // ============================================
        // 2. USER AGENT CONSISTENCY (15 points)
        // ============================================
        $ua_score = self::checkUserAgentConsistency($user_agent, $fingerprint);
        $score += $ua_score['score'];
        if ($ua_score['score'] > 0) {
            $reasons = array_merge($reasons, $ua_score['reasons']);
        } else {
            $checks_passed[] = 'user_agent_consistent';
        }
        
        // ============================================
        // 3. BROWSER FINGERPRINT VALIDATION (20 points)
        // ============================================
        $fp_score = self::validateFingerprint($fingerprint);
        $score += $fp_score['score'];
        if ($fp_score['score'] > 0) {
            $reasons = array_merge($reasons, $fp_score['reasons']);
        } else {
            $checks_passed[] = 'fingerprint_valid';
        }
        
        // ============================================
        // 4. JAVASCRIPT CHALLENGE VERIFICATION (15 points)
        // ============================================
        $js_score = self::verifyJavaScriptChallenge($request_data);
        $score += $js_score['score'];
        if ($js_score['score'] > 0) {
            $reasons = array_merge($reasons, $js_score['reasons']);
        } else {
            $checks_passed[] = 'javascript_executed';
        }
        
        // ============================================
        // 5. TIMING ANALYSIS (10 points)
        // ============================================
        $timing_score = self::analyzeTimingPatterns($request_data);
        $score += $timing_score['score'];
        if ($timing_score['score'] > 0) {
            $reasons = array_merge($reasons, $timing_score['reasons']);
        } else {
            $checks_passed[] = 'timing_natural';
        }
        
        // ============================================
        // 6. IP REPUTATION (10 points)
        // ============================================
        $ip_score = self::checkIPReputation($ip);
        $score += $ip_score['score'];
        if ($ip_score['score'] > 0) {
            $reasons = array_merge($reasons, $ip_score['reasons']);
        } else {
            $checks_passed[] = 'ip_clean';
        }
        
        // ============================================
        // 7. BEHAVIORAL PATTERNS (10 points)
        // ============================================
        $behavior_score = self::analyzeBehavior($request_data);
        $score += $behavior_score['score'];
        if ($behavior_score['score'] > 0) {
            $reasons = array_merge($reasons, $behavior_score['reasons']);
        } else {
            $checks_passed[] = 'behavior_human';
        }
        
        // Calculate final probability (0-100%)
        $bot_probability = min(100, $score);
        $human_probability = max(0, 100 - $bot_probability);
        
        // Decision: Block if score > 70 (>70% bot probability)
        // Made more lenient to avoid false positives
        $is_bot = $bot_probability > 70;
        
        return [
            'is_bot' => $is_bot,
            'is_human' => !$is_bot,
            'bot_probability' => $bot_probability,
            'human_probability' => $human_probability,
            'score' => $score,
            'max_score' => $max_score,
            'reasons' => $reasons,
            'checks_passed' => $checks_passed,
            'confidence' => self::calculateConfidence($score, $reasons)
        ];
    }
    
    /**
     * Analyze HTTP headers for bot patterns
     */
    private static function analyzeHeaders($headers) {
        $score = 0;
        $reasons = [];
        
        // Missing Accept header (bots often forget this)
        if (empty($headers['HTTP_ACCEPT'])) {
            $score += 5;
            $reasons[] = 'missing_accept_header';
        }
        
        // Missing Accept-Language (bots often forget this)
        if (empty($headers['HTTP_ACCEPT_LANGUAGE'])) {
            $score += 5;
            $reasons[] = 'missing_accept_language';
        }
        
        // Missing Accept-Encoding (suspicious)
        if (empty($headers['HTTP_ACCEPT_ENCODING'])) {
            $score += 3;
            $reasons[] = 'missing_accept_encoding';
        }
        
        // Check for bot-like Connection header
        if (isset($headers['HTTP_CONNECTION']) && 
            strtolower($headers['HTTP_CONNECTION']) === 'close') {
            $score += 2;
            $reasons[] = 'connection_close';
        }
        
        // Check for suspicious header order (advanced)
        if (self::detectSuspiciousHeaderOrder($headers)) {
            $score += 5;
            $reasons[] = 'suspicious_header_order';
        }
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Check user agent consistency with fingerprint
     */
    private static function checkUserAgentConsistency($user_agent, $fingerprint) {
        $score = 0;
        $reasons = [];
        
        if (empty($fingerprint)) {
            return ['score' => 0, 'reasons' => []];
        }
        
        // Extract browser from UA
        $ua_lower = strtolower($user_agent);
        
        // Check platform consistency
        if (isset($fingerprint['platform'])) {
            $claimed_platform = $fingerprint['platform'];
            
            // Windows check
            if ((strpos($ua_lower, 'windows') !== false) !== 
                (strpos(strtolower($claimed_platform), 'win') !== false)) {
                $score += 5;
                $reasons[] = 'platform_mismatch';
            }
            
            // Mac check
            if ((strpos($ua_lower, 'mac') !== false) !== 
                (strpos(strtolower($claimed_platform), 'mac') !== false)) {
                $score += 5;
                $reasons[] = 'platform_mismatch';
            }
        }
        
        // Check browser consistency
        if (isset($fingerprint['vendor'])) {
            $vendor = strtolower($fingerprint['vendor']);
            
            // Chrome should have Google Inc
            if (strpos($ua_lower, 'chrome') !== false && 
                strpos($vendor, 'google') === false) {
                $score += 5;
                $reasons[] = 'vendor_mismatch';
            }
        }
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Validate browser fingerprint for anomalies
     */
    private static function validateFingerprint($fingerprint) {
        $score = 0;
        $reasons = [];
        
        // Don't penalize missing fingerprint - may not be sent yet
        if (empty($fingerprint)) {
            return ['score' => 0, 'reasons' => []];
        }
        
        // Check for required properties
        $required = ['screen_width', 'screen_height', 'timezone', 'language', 'platform'];
        foreach ($required as $prop) {
            if (!isset($fingerprint[$prop]) || empty($fingerprint[$prop])) {
                $score += 2;
                $reasons[] = 'missing_' . $prop;
            }
        }
        
        // Suspicious screen resolutions (common headless browser defaults)
        if (isset($fingerprint['screen_width']) && isset($fingerprint['screen_height'])) {
            $width = $fingerprint['screen_width'];
            $height = $fingerprint['screen_height'];
            
            // Headless Chrome default
            if ($width == 800 && $height == 600) {
                $score += 10;
                $reasons[] = 'headless_screen_resolution';
            }
            
            // PhantomJS default
            if ($width == 400 && $height == 300) {
                $score += 10;
                $reasons[] = 'phantomjs_screen_resolution';
            }
        }
        
        // Check WebGL support (real browsers have it)
        if (isset($fingerprint['webgl']) && !$fingerprint['webgl']) {
            $score += 5;
            $reasons[] = 'no_webgl';
        }
        
        // Check Canvas support (real browsers have it)
        if (isset($fingerprint['canvas']) && empty($fingerprint['canvas'])) {
            $score += 5;
            $reasons[] = 'no_canvas';
        }
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Verify JavaScript execution challenge
     */
    private static function verifyJavaScriptChallenge($request_data) {
        $score = 0;
        $reasons = [];
        
        // Don't penalize missing challenge - may not be sent yet
        if (!isset($request_data['js_challenge']) || 
            empty($request_data['js_challenge'])) {
            return ['score' => 0, 'reasons' => []];
        }
        
        $challenge = $request_data['js_challenge'];
        
        // Verify challenge solution
        $expected = self::calculateJSChallenge($request_data['challenge_seed'] ?? '');
        if ($challenge !== $expected) {
            $score += 15;
            $reasons[] = 'invalid_js_challenge';
        }
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Analyze timing patterns
     */
    private static function analyzeTimingPatterns($request_data) {
        $score = 0;
        $reasons = [];
        
        // Check time to complete (from page load to API call)
        if (isset($request_data['timing'])) {
            $time_ms = $request_data['timing'];
            
            // Too fast (< 100ms) - likely bot
            if ($time_ms < 100) {
                $score += 10;
                $reasons[] = 'too_fast';
            }
            
            // Suspiciously exact times (bots often use setTimeout(0))
            if ($time_ms == 0 || $time_ms == 10 || $time_ms == 100) {
                $score += 5;
                $reasons[] = 'exact_timing';
            }
        }
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Check IP reputation against known bot networks
     */
    private static function checkIPReputation($ip) {
        $score = 0;
        $reasons = [];
        
        // Known cloud/hosting provider IPs (often used by bots)
        $cloud_providers = [
            '35.', '54.', '52.', // AWS
            '104.', '34.', // Google Cloud
            '40.', '13.', // Azure
        ];
        
        foreach ($cloud_providers as $prefix) {
            if (strpos($ip, $prefix) === 0) {
                $score += 3;
                $reasons[] = 'cloud_hosting_ip';
                break;
            }
        }
        
        // Check for Tor exit nodes (optional - can add list)
        // This is simplified - you can add a full Tor exit node list
        
        return ['score' => $score, 'reasons' => $reasons];
    }
    
    /**
     * Analyze behavioral patterns
     */
    private static function analyzeBehavior($request_data) {
        $score = 0;
        $reasons = [];
        
        // Behavioral checks are now informational only
        // Don't penalize - users might load page quickly
        // These checks are more useful for pattern analysis over time
        
        return ['score' => 0, 'reasons' => []]; // Disabled for now - too strict
    }
    
    /**
     * Calculate confidence level
     */
    private static function calculateConfidence($score, $reasons) {
        $confidence = 'medium';
        
        if ($score > 70) {
            $confidence = 'very_high'; // Very confident it's a bot
        } elseif ($score > 50) {
            $confidence = 'high';
        } elseif ($score > 30) {
            $confidence = 'medium';
        } elseif ($score > 10) {
            $confidence = 'low';
        } else {
            $confidence = 'very_low'; // Very confident it's human
        }
        
        return $confidence;
    }
    
    /**
     * Detect suspicious header order (advanced check)
     */
    private static function detectSuspiciousHeaderOrder($headers) {
        // Real browsers send headers in specific order
        // This is a simplified check - can be enhanced
        return false; // Placeholder
    }
    
    /**
     * Calculate JavaScript challenge solution
     */
    private static function calculateJSChallenge($seed) {
        $str = $seed . 'challenge_secret_salt';
        $hash = 0;
        $len = strlen($str);
        
        for ($i = 0; $i < $len; $i++) {
            $char = ord($str[$i]);
            // Replicate JS 32-bit signed integer rolling hash
            $hash = (($hash << 5) - $hash) + $char;
            $hash = $hash & 0xFFFFFFFF; // Keep it to 32 bits
        }
        
        // Convert to signed 32-bit if needed (PHP integers can be 64-bit)
        if ($hash & 0x80000000) {
            $hash = $hash - 0x100000000;
        }
        
        return dechex(abs($hash));
    }
}

/**
 * reCAPTCHA v3 Integration (Legacy - kept for backward compatibility)
 */
class RecaptchaV3 {
    
    /**
     * Verify reCAPTCHA v3 token
     * @param string $token Token from client
     * @param string $ip Visitor IP
     * @return array Verification result with score
     */
    public static function verify($token, $ip) {
        // Load configuration
        require_once __DIR__ . '/config_recaptcha.php';
        
        if (!RECAPTCHA_ENABLED) {
            return [
                'success' => true,
                'score' => 1.0,
                'disabled' => true
            ];
        }
        
        if (empty($token)) {
            // Token missing - could be ad blocker, config mismatch, or slow loading
            // Use fallback mode to allow visitor (they're still protected by 9 other layers)
            return [
                'success' => true,      // Allow visitor (fallback mode)
                'score' => 0.5,         // Neutral score
                'error' => 'missing_token',
                'fallback' => true,     // Flag for logging
                'reason' => 'Client did not send reCAPTCHA token - possible ad blocker or configuration mismatch'
            ];
        }
        
        // Verify with Google
        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = [
            'secret' => RECAPTCHA_SECRET_KEY,
            'response' => $token,
            'remoteip' => $ip
        ];
        
        $options = [
            'http' => [
                'header' => "Content-type: application/x-www-form-urlencoded\r\n",
                'method' => 'POST',
                'content' => http_build_query($data),
                'timeout' => 10
            ]
        ];
        
        $context = stream_context_create($options);
        $result = @file_get_contents($url, false, $context);
        
        if ($result === false) {
            // reCAPTCHA service unavailable - allow by default
            return [
                'success' => true,
                'score' => 0.5,
                'error' => 'service_unavailable',
                'fallback' => true
            ];
        }
        
        $response = json_decode($result, true);
        
        // Check if verification succeeded
        if (!isset($response['success']) || !$response['success']) {
            return [
                'success' => false,
                'score' => 0.0,
                'error' => 'verification_failed',
                'error_codes' => $response['error-codes'] ?? []
            ];
        }
        
        $score = $response['score'] ?? 0.0;
        
        // Check score threshold
        $passed = $score >= RECAPTCHA_SCORE_THRESHOLD;
        
        return [
            'success' => $passed,
            'score' => $score,
            'action' => $response['action'] ?? '',
            'hostname' => $response['hostname'] ?? '',
            'challenge_ts' => $response['challenge_ts'] ?? '',
            'threshold' => RECAPTCHA_SCORE_THRESHOLD,
            'passed_threshold' => $passed
        ];
    }
}

/**
 * reCAPTCHA v2 Integration
 * Requires user to solve a visible challenge (checkbox or image challenge)
 * More secure than v3 as it requires active human interaction
 */
class RecaptchaV2 {
    
    /**
     * Verify reCAPTCHA v2 token
     * @param string $token Token from client (user must solve challenge first)
     * @param string $ip Visitor IP
     * @return array Verification result
     */
    public static function verify($token, $ip) {
        // Load configuration
        require_once __DIR__ . '/config_recaptcha.php';
        
        if (!RECAPTCHA_ENABLED) {
            return [
                'success' => true,
                'disabled' => true
            ];
        }
        
        // v2 REQUIRES token - no fallback mode for security
        if (empty($token)) {
            return [
                'success' => false,
                'error' => 'missing_token',
                'reason' => 'reCAPTCHA v2 requires user to solve challenge. Token is missing.'
            ];
        }
        
        // Verify with Google
        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = [
            'secret' => RECAPTCHA_SECRET_KEY,
            'response' => $token,
            'remoteip' => $ip
        ];
        
        $options = [
            'http' => [
                'header' => "Content-type: application/x-www-form-urlencoded\r\n",
                'method' => 'POST',
                'content' => http_build_query($data),
                'timeout' => 10
            ]
        ];
        
        $context = stream_context_create($options);
        $result = @file_get_contents($url, false, $context);
        
        if ($result === false) {
            // reCAPTCHA service unavailable - block for security
            return [
                'success' => false,
                'error' => 'service_unavailable',
                'reason' => 'Unable to verify reCAPTCHA. Please try again.'
            ];
        }
        
        $response = json_decode($result, true);
        
        // Check if verification succeeded
        if (!isset($response['success']) || !$response['success']) {
            return [
                'success' => false,
                'error' => 'verification_failed',
                'error_codes' => $response['error-codes'] ?? [],
                'reason' => 'reCAPTCHA verification failed. Please solve the challenge again.'
            ];
        }
        
        // v2 doesn't have score - just success/failure
        return [
            'success' => true,
            'hostname' => $response['hostname'] ?? '',
            'challenge_ts' => $response['challenge_ts'] ?? ''
        ];
    }
}

