<?php
/**
 * WordPress Vulnerability Protection for BotFend Anti-Bot Firewall
 *
 * @package BotFend\Security
 * @since 4.2.0
 * @version 4.8.3 - Rebranded from SafeBot, geolocation opt-in added
 */

namespace BotFend\Security;

use BotFend\Core\Config;
use BotFend\Utilities\Utilities;
use BotFend\Database\Database;
use BotFend\ThirdParty\Recaptcha;
use WP_Error;

defined('ABSPATH') || exit;

/**
 * Class WordPressProtection
 *
 * Provides multi-layered security for WordPress installations.
 */
class WordPressProtection {

    private Config $config;
    private Utilities $utilities;
    private Database $database;
    private ?Recaptcha $recaptcha = null;
    
    private static ?array $compiled_malicious_patterns = null;
    private static array $scan_cache = [];
    private static array $country_whitelist_cache = [];
    private static array $verified_crawler_cache = [];

    private const MAX_CACHE_SIZE = 100;
    private const MAX_COUNTRY_CACHE = 50;
    private const MAX_VERIFIED_CRAWLER_CACHE = 200;
    private const MAX_PARAM_LENGTH = 10000;

    private array $xmlrpc_attack_patterns = [];
    private array $rest_attack_patterns = [];
    private array $login_attack_patterns = [];
    private array $whitelisted_php_files = [];
    private array $whitelisted_extensions = [];
    private array $allowed_usernames = [];

    public function __construct(Utilities $utilities, Database $database, ?Recaptcha $recaptcha = null) {
        $this->config = Config::get_instance();
        $this->utilities = $utilities;
        $this->database = $database;
        $this->recaptcha = $recaptcha;
        
        $this->init_arrays();

        add_action('botfend_auto_unblock', [$this, 'auto_unblock_false_positives']);

        if ($this->is_enabled()) {
            $this->initialize_protection();
        }
    }
    
    private function init_arrays(): void {
        $this->xmlrpc_attack_patterns = [
            'system.multicall'    => ['score' => 8, 'type' => 'xmlrpc_ddos'],
            'pingback.ping'       => ['score' => 6, 'type' => 'pingback_attack'],
            'wp.getUsersBlogs'    => ['score' => 7, 'type' => 'enumeration'],
            'demo.sayHello'       => ['score' => 5, 'type' => 'probe'],
            'pingback.extensions' => ['score' => 6, 'type' => 'reconnaissance'],
        ];

        $this->rest_attack_patterns = [
            '/wp/v2/users'        => ['score' => 8, 'type' => 'user_enumeration'],
            '/wp/v2/comments'     => ['score' => 6, 'type' => 'comment_spam'],
        ];

        $this->login_attack_patterns = [
            'admin'               => ['score' => 4, 'type' => 'common_username'],
            'administrator'       => ['score' => 4, 'type' => 'common_username'],
        ];
        
        $this->whitelisted_php_files = [
            'header.php', 'footer.php', 'sidebar.php', 'functions.php', 'index.php',
            'single.php', 'page.php', 'archive.php', 'search.php', '404.php',
            'comments.php', 'style.css', 'ajax.php', 'submit.php', 'contact.php', 
            'form.php', 'process.php', 'upload.php', 'download.php', 'payment.php', 
            'callback.php', 'api.php', 'webhook.php', 'cron.php', 'cart.php', 
            'checkout.php', 'account.php', 'orders.php', 'thankyou.php', 'confirmation.php',
            'login.php', 'register.php', 'profile.php', 'reset.php', 'activate.php', 
            'subscription.php', 'sitemap.php', 'feed.php', 'rss.php', 'atom.php',
            'captcha.php', 'recaptcha.php', 'security.php', 'test.php', 'debug.php', 
            'maintenance.php',
        ];
        
        $this->whitelisted_extensions = [
            '.css', '.js', '.jpg', '.jpeg', '.png', '.gif', '.svg',
            '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
            '.mp3', '.mp4', '.avi', '.mov', '.wmv',
            '.zip', '.rar', '.7z', '.tar', '.gz',
            '.txt', '.csv', '.json', '.xml',
            '.woff', '.woff2', '.ttf', '.eot', '.otf',
            '.ico', '.webp', '.bmp', '.tiff',
        ];
        
        $this->allowed_usernames = [
            'user', 'test', 'demo', 'guest', 'member', 'author', 'editor'
        ];
    }

    private function is_enabled(): bool {
        return $this->config->get_bool('enable_wp_protection', true);
    }

    private function is_attack_blocking_enabled(): bool {
        return $this->config->get_bool('block_wp_attacks', true);
    }

    private function get_attack_block_duration(): int {
        return $this->config->get_int('wp_attack_block_duration', 24);
    }

    private function get_brute_force_block_duration(): int {
        return $this->config->get_int('brute_force_block_duration', 24);
    }

    private function get_max_login_failures(): int {
        return $this->config->get_int('max_login_failures', 5);
    }

    private function get_login_failure_window(): int {
        return $this->config->get_int('login_failure_window', 900);
    }

    private function initialize_protection(): void {
        add_action('init', [$this, 'protect_wordpress'], 5);

        if ($this->config->get_bool('disable_xmlrpc', false)) {
            add_filter('xmlrpc_enabled', '__return_false');
            add_filter('xmlrpc_methods', '__return_empty_array');
            add_action('init', [$this, 'block_xmlrpc_direct_access']);
        } else {
            add_action('xmlrpc_call', [$this, 'monitor_xmlrpc_calls'], 10, 1);
        }

        add_filter('rest_authentication_errors', [$this, 'rest_api_protection'], 10, 1);
        add_action('rest_api_init', [$this, 'monitor_rest_api_calls'], 1);

        add_action('login_init', [$this, 'protect_login_page']);
        add_action('wp_login_failed', [$this, 'track_login_failure'], 10, 1);
        add_filter('authenticate', [$this, 'check_login_attempts'], 30, 3);

        if ($this->config->get_bool('enable_comment_protection', true)) {
            add_filter('pre_comment_approved', [$this, 'block_comment_links'], 10, 2);
            add_filter('preprocess_comment', [$this, 'block_spam_keywords'], 10, 1);
            add_filter('preprocess_comment', [$this, 'block_comment_from_blocked_ip'], 5, 1);
        }

        add_action('manage_comments_custom_column', [$this, 'render_comment_spam_flags'], 10, 2);
        add_action('admin_init', [$this, 'monitor_admin_ajax']);
        add_filter('query_vars', [$this, 'block_author_enumeration']);
        add_action('template_redirect', [$this, 'prevent_author_scans']);
        add_action('wp', [$this, 'prevent_pingback_attacks']);
        add_action('send_headers', [$this, 'add_security_headers']);
        add_filter('botfend_should_block_ip', [$this, 'country_whitelist_check'], 10, 3);

        if (!$this->config->get_bool('enable_wpcontent_protection', false)) {
            remove_action('init', [$this, 'protect_wordpress'], 5);
            add_action('init', [$this, 'protect_wordpress_minimal'], 5);
        }
    }

    private function is_verified_crawler(string $ip): bool {
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }
        
        $cache_key = md5($ip);
        
        if (isset(self::$verified_crawler_cache[$cache_key])) {
            return self::$verified_crawler_cache[$cache_key];
        }
        
        if (count(self::$verified_crawler_cache) >= self::MAX_VERIFIED_CRAWLER_CACHE) {
            self::$verified_crawler_cache = array_slice(
                self::$verified_crawler_cache, 
                50, 
                null, 
                true
            );
        }
        
        $user_agent = $this->utilities->get_user_agent();
        
        if ($this->utilities->verify_legitimate_crawler($ip, $user_agent)) {
            self::$verified_crawler_cache[$cache_key] = true;
            $this->log_crawler_bypass($ip, $user_agent);
            return true;
        }
        
        self::$verified_crawler_cache[$cache_key] = false;
        return false;
    }
    
    private function log_crawler_bypass(string $ip, string $user_agent): void {
        if (!$this->database || !method_exists($this->database, 'log_request')) {
            return;
        }
        
        $crawler = $this->utilities->extract_crawler_from_ua($user_agent) ?: 'unknown';
        $request_uri = isset($_SERVER['REQUEST_URI']) ? 
            sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        
        $log_key = 'botfend_crawler_bypass_' . md5($ip . $crawler . gmdate('Y-m-d-H'));
        
        if (!get_transient($log_key)) {
            $this->database->log_request(
                $ip,
                0,
                'crawler_bypass',
                [
                    sprintf('Verified crawler bypass: %s', $crawler),
                    sprintf('URI: %s', $request_uri)
                ],
                $user_agent,
                true,
                $this->utilities->get_country($ip)
            );
            
            set_transient($log_key, true, HOUR_IN_SECONDS);
        }
    }

    private function should_bypass_protection(string $ip): bool {
        if ($this->is_verified_crawler($ip)) {
            return true;
        }
        if ($this->utilities->is_ip_whitelisted($ip)) {
            return true;
        }
        return false;
    }

    /**
     * PATCHED: Combined multiple LIKE queries into a single, optimized SQL operation.
     */
    public function auto_unblock_false_positives(): void {
        global $wpdb;

        $blocklist_table = $wpdb->prefix . 'botfend_blocklist';

        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $blocklist_table)) !== $blocklist_table) {
            return;
        }

        $false_positive_reasons = [
            'wp-content attack: PHP file access in wp-content directory',
            'wp-content attack: Direct themes access',
            'wp-content attack: Direct uploads access',
            'wp-content attack: Direct plugins access',
            'PHP file access in uploads directory',
            'PHP file access in themes directory',
        ];

        // Build a single query to prevent DB DoS
        $where_clauses = [];
        $params = [];
        
        foreach ($false_positive_reasons as $reason) {
            $where_clauses[] = "reason LIKE %s";
            $params[] = '%' . $wpdb->esc_like($reason) . '%';
            
            // Clear the transients efficiently
            delete_transient('botfend_fp_' . md5($reason));
        }

        if (!empty($where_clauses)) {
            $sql = "DELETE FROM `{$blocklist_table}` 
                    WHERE (" . implode(' OR ', $where_clauses) . ") 
                    AND created_at > DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR)";
            $wpdb->query($wpdb->prepare($sql, ...$params));
        }
    }

    // ==================== COMMENT PROTECTION ====================

    public function block_comment_from_blocked_ip(array $commentdata): array {
        $ip = $this->utilities->get_client_ip();
        
        if ($this->should_bypass_protection($ip)) {
            return $commentdata;
        }
        
        if ($this->database->is_ip_blocked($ip)) {
            wp_die(
                esc_html__('Your IP address has been blocked from posting comments.', 'botfend-anti-bot-firewall'),
                esc_html__('Comment Blocked', 'botfend-anti-bot-firewall'),
                ['response' => 403]
            );
        }
        
        return $commentdata;
    }

    public function block_comment_links($approved, array $commentdata) {
        $ip = $this->utilities->get_client_ip();
        
        if ($this->should_bypass_protection($ip)) {
            return $approved;
        }
        
        // Ensure variables are strings before regex checking to prevent TypeErrors
        $content = isset($commentdata['comment_content']) && is_string($commentdata['comment_content']) ? $commentdata['comment_content'] : '';
        $author = isset($commentdata['comment_author']) && is_string($commentdata['comment_author']) ? $commentdata['comment_author'] : '';
        $url = isset($commentdata['comment_author_url']) && is_string($commentdata['comment_author_url']) ? $commentdata['comment_author_url'] : '';
        
        if (!empty($url)) {
            $this->log_spam_comment($ip, 'url_field_present', $commentdata);
            return 'spam';
        }
        if ($this->contains_links($content)) {
            $this->log_spam_comment($ip, 'link_in_content', $commentdata);
            return 'spam';
        }
        if ($this->contains_html_or_links($author)) {
            $this->log_spam_comment($ip, 'html_in_author', $commentdata);
            return 'spam';
        }
        
        return $approved;
    }
    
    public function block_spam_keywords(array $commentdata): array {
        $ip = $this->utilities->get_client_ip();
        
        if ($this->should_bypass_protection($ip)) {
            return $commentdata;
        }
        
        if ($this->contains_spam_keywords($commentdata)) {
            $commentdata['comment_approved'] = 'spam';
            $this->log_spam_comment($ip, 'spam_keywords', $commentdata);
        }
        
        return $commentdata;
    }
    
    private function contains_links(string $text): bool {
        if (preg_match('/href=[\'"]([^\'"]+)[\'"]/i', $text)) return true;
        if (preg_match('#\bhttps?://[^\s()<>]+#i', $text)) return true;
        if (preg_match('#\bwww\.[^\s()<>]+#i', $text)) return true;
        if (preg_match('#\[.*?\]\(https?://#i', $text)) return true;
        if (preg_match('#\[url[=\]]#i', $text)) return true;
        
        return false;
    }
    
    private function contains_html_or_links(string $text): bool {
        if (strip_tags($text) !== $text) {
            return true;
        }
        return $this->contains_links($text);
    }
    
    private function contains_spam_keywords(array $commentdata): bool {
        $spam_keywords = [
            'viagra', 'cialis', 'levitra', 'casino', 'poker', 'betting',
            'payday loan', 'essay writing', 'dissertation', 'thesis',
            'seo services', 'backlinks', 'guest post', 'buy cheap',
            'discount', 'pharmacy', 'medication', 'pills',
            'bitcoin', 'cryptocurrency', 'investment', 'earn money',
            'work from home', 'make money online', 'passive income',
            'weight loss', 'diet pills', 'muscle building',
            'xxx', 'porn', 'adult', 'escort',
        ];
        
        $check_text = strtolower(
            (is_string($commentdata['comment_content'] ?? null) ? $commentdata['comment_content'] : '') . ' ' .
            (is_string($commentdata['comment_author'] ?? null) ? $commentdata['comment_author'] : '') . ' ' .
            (is_string($commentdata['comment_author_url'] ?? null) ? $commentdata['comment_author_url'] : '') . ' ' .
            (is_string($commentdata['comment_author_email'] ?? null) ? $commentdata['comment_author_email'] : '')
        );
        
        foreach ($spam_keywords as $keyword) {
            if (stripos($check_text, $keyword) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    private function log_spam_comment(string $ip, string $reason, array $commentdata): void {
        if (!$this->database || !method_exists($this->database, 'log_request')) {
            return;
        }
        
        $user_agent = $this->utilities->get_user_agent();
        $log_key = 'botfend_spam_log_' . md5($ip . $reason . gmdate('Y-m-d-H-i'));
        
        if (!get_transient($log_key)) {
            $this->database->log_request(
                $ip,
                7,
                'comment_spam',
                [
                    sprintf('Comment spam blocked: %s', $reason),
                    isset($commentdata['comment_author']) && is_string($commentdata['comment_author'])
                        ? 'Author: ' . sanitize_text_field($commentdata['comment_author']) 
                        : ''
                ],
                $user_agent,
                false,
                $this->utilities->get_country($ip)
            );
            
            set_transient($log_key, true, 60);
        }
    }

    public function render_comment_spam_flags(string $column_name, int $comment_id): void {
        if ('comment' !== $column_name) return;

        $flags = get_comment_meta($comment_id, 'botfend_flagged', true);
        if (empty($flags)) return;
        
        // Ensure flag is a string to prevent sprintf Array to String conversion errors
        $flag_str = is_array($flags) ? implode(', ', $flags) : (string)$flags;

        printf(
            '<span class="botfend-flag-badge" style="display:inline-block;background:#fff3cd;color:#856404;padding:2px 6px;border-radius:3px;font-size:11px;margin-left:5px;" title="%s">⚠ %s</span>',
            esc_attr(sprintf(esc_html__('BotFend flagged: %s', 'botfend-anti-bot-firewall'), $flag_str)),
            esc_html__('Reviewed', 'botfend-anti-bot-firewall')
        );
    }

    // ==================== REMAINING PROTECTION METHODS ====================

    private function get_whitelisted_countries_cached(): array {
        $cache_key = 'whitelisted_countries';

        if (isset(self::$country_whitelist_cache[$cache_key])) {
            return self::$country_whitelist_cache[$cache_key];
        }

        $countries = $this->config->get_array('whitelist_countries', []);
        if (empty($countries)) {
            self::$country_whitelist_cache[$cache_key] = [];
            return [];
        }

        $valid_countries = [];
        foreach ($countries as $country) {
            $country = trim(strtoupper($country));
            if (preg_match('/^[A-Z]{2}$/', $country)) {
                $valid_countries[] = $country;
            }
        }

        $valid_countries = array_unique($valid_countries);
        self::$country_whitelist_cache[$cache_key] = $valid_countries;

        if (count(self::$country_whitelist_cache) > self::MAX_COUNTRY_CACHE) {
            self::$country_whitelist_cache = array_slice(self::$country_whitelist_cache, -20, null, true);
        }

        return $valid_countries;
    }

    public function country_whitelist_check(bool $should_block, string $ip, int $score): bool {
        static $log_cache = [];

        if (!$this->config->get_bool('enable_country_whitelist', true)) {
            return $should_block;
        }

        // CRITICAL: Geolocation requires opt-in (disabled by default)
        $country = $this->utilities->get_country($ip);
        if (empty($country)) {
            return $should_block;
        }

        $whitelisted_countries = $this->get_whitelisted_countries_cached();

        if (in_array($country, $whitelisted_countries, true)) {
            $max_score = $this->config->get_int('whitelist_max_score', 8);
            $log_key = md5($ip . gmdate('Y-m-d-H'));

            if (!isset($log_cache[$log_key])) {
                $this->log_whitelisted_traffic($ip, $country, $score);
                $log_cache[$log_key] = true;
            }

            if ($score < $max_score) return false;

            if ($score >= $max_score) {
                $this->log_whitelisted_attack($ip, $country, $score);
            }

            if ($score >= 9) return true;

            return false;
        }

        return $should_block;
    }

    private function log_whitelisted_traffic(string $ip, string $country, int $score): void {
        if (!$this->database || !method_exists($this->database, 'log_request')) return;

        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';

        if (wp_rand(1, 100) <= 10) {
            $this->database->log_request(
                $ip, $score, 'whitelisted_traffic',
                [
                    sprintf('Country: %s - Whitelisted traffic', $country),
                    sprintf('Score: %d', $score)
                ],
                $user_agent, false, $country
            );
        }
    }

    private function log_whitelisted_attack(string $ip, string $country, int $score): void {
        if (!$this->database || !method_exists($this->database, 'log_request')) return;

        $transient_key = 'botfend_wl_attack_' . md5($ip);
        $count         = (int) get_transient($transient_key);
        $count++;

        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';

        if ($count === 1 || wp_rand(1, 100) <= 25) {
            $this->database->log_request(
                $ip, $score, 'whitelisted_high_score',
                [
                    sprintf('Country: %s - Score: %d', $country, $score),
                    esc_html__('High-score request from whitelisted country', 'botfend-anti-bot-firewall')
                ],
                $user_agent, true, $country
            );
            $count = 0;
        }

        set_transient($transient_key, $count, HOUR_IN_SECONDS);
    }

    private function get_malicious_patterns(): array {
        if (self::$compiled_malicious_patterns === null) {
            self::$compiled_malicious_patterns = [
                '#union\s+select\s+.+from#i'                       => 9,
                '#select\s+.+\s+from\s+.+\s+where#i'               => 8,
                '#insert\s+into\s+.+\s+values#i'                   => 8,
                '#update\s+.+\s+set\s+.+\s+where#i'                => 8,
                '#delete\s+from\s+.+\s+where#i'                    => 8,
                '#drop\s+table\s+.+#i'                             => 9,
                '#<script\b[^>]*>.*</script>#i'                    => 7,
                '#eval\s*\(\s*[^)]+#i'                             => 8,
                '#\|\s*(bash|sh|cmd|powershell)#i'                 => 9,
                '#<\?php\s+.*\?>#i'                                => 9,
                '#php://(input|stdin|memory|temp)#i'               => 9,
                '#(\.\./){3,}#'                                    => 8,
                '#/etc/passwd#i'                                   => 10,
                '#http://169\.254\.169\.254#i'                     => 9,
            ];
        }
        return self::$compiled_malicious_patterns;
    }

    public function block_xmlrpc_direct_access(): void {
        if (isset($_SERVER['REQUEST_URI']) && strpos(wp_unslash($_SERVER['REQUEST_URI']), 'xmlrpc.php') !== false) {
            $this->send_security_response(403, esc_html__('XML-RPC is disabled', 'botfend-anti-bot-firewall'));
        }
    }

    public function protect_wordpress(): void {
        $this->check_current_request();
    }

    /**
     * PATCHED: Scanning MUST use raw URI to prevent attackers exploiting sanitization functions to mask payloads.
     */
    public function protect_wordpress_minimal(): void {
        $ip = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip)) {
            return;
        }

        $user_agent  = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        // CRITICAL: WAF pattern matching must operate on the raw payload. Do not use esc_url_raw here.
        $raw_request_uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';

        $score   = 0;
        $factors = [];

        $score += $this->detect_wp_admin_attacks($raw_request_uri, $factors);
        $score += $this->detect_config_file_access($raw_request_uri, $factors);
        $score += $this->detect_php_file_access($raw_request_uri, $factors);

        if (!empty($_GET)) {
            $score += $this->detect_malicious_parameters($_GET, $factors, 'GET');
        }

        $should_block = apply_filters('botfend_should_block_ip', true, $ip, $score);
        $threshold = $this->config->get_int('wp_protection_threshold', 10);
        
        if ($score >= $threshold && !empty($factors) && $should_block) {
            // Only escape the URI when preparing to log/output
            $this->handle_wp_attack($ip, $score, $factors, esc_url_raw($raw_request_uri), $user_agent);
        }
    }

    /**
     * PATCHED: Scan raw URI to prevent WAF blindness.
     */
    private function check_current_request(): void {
        $ip = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip)) {
            return;
        }

        $user_agent  = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        // CRITICAL: Raw URI scan
        $raw_request_uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';

        $score   = 0;
        $factors = [];

        $score += $this->detect_wp_admin_attacks($raw_request_uri, $factors);
        $score += $this->detect_config_file_access($raw_request_uri, $factors);
        $score += $this->detect_wp_content_attacks($raw_request_uri, $factors);
        $score += $this->detect_php_file_access($raw_request_uri, $factors);

        if (!empty($_GET)) {
            $score += $this->detect_malicious_parameters($_GET, $factors, 'GET');
        }
        if (!empty($_POST)) {
            $score += $this->detect_malicious_parameters($_POST, $factors, 'POST');
        }

        $should_block = apply_filters('botfend_should_block_ip', true, $ip, $score);
        $threshold = $this->config->get_int('wp_protection_threshold', 10);
        
        if ($score >= $threshold && !empty($factors) && $should_block) {
            $this->handle_wp_attack($ip, $score, $factors, esc_url_raw($raw_request_uri), $user_agent);
        }
    }

    private function detect_wp_content_attacks(string $request_uri, array &$factors): int {
        $score = 0;

        if (strpos($request_uri, '/wp-content/') === false) return $score;

        foreach ($this->whitelisted_extensions as $ext) {
            if (stripos($request_uri, $ext) !== false) return $score;
        }

        $attack_patterns = [
            '#wp-config\.php($|\?)#i'                              => ['score' => 10, 'reason' => 'Config file in wp-content'],
            '#\.sql($|\?)#i'                                       => ['score' => 8, 'reason' => 'SQL file access'],
            '#\.env($|\?)#i'                                       => ['score' => 9, 'reason' => 'Environment file access'],
            '#shell\.php($|\?)#i'                                  => ['score' => 10, 'reason' => 'Shell script'],
            '#c99\.php($|\?)#i'                                    => ['score' => 10, 'reason' => 'Malicious script'],
            '#wso\.php($|\?)#i'                                    => ['score' => 10, 'reason' => 'Webshell'],
            '#(\.\./){2,}#'                                        => ['score' => 8, 'reason' => 'Directory traversal'],
        ];

        foreach ($attack_patterns as $pattern => $info) {
            if (preg_match($pattern, $request_uri)) {
                $score += $info['score'];
                $factors[] = 'wp-content security: ' . $info['reason'];
                break;
            }
        }

        return $score;
    }

    private function detect_config_file_access(string $request_uri, array &$factors): int {
        $score = 0;

        $config_files = [
            'wp-config.php'     => ['score' => 10, 'type' => 'core_config'],
            'wp-config.bak'     => ['score' => 9, 'type' => 'backup_config'],
            '.env'              => ['score' => 9, 'type' => 'env_file'],
            '.git/'             => ['score' => 8, 'type' => 'git_directory'],
        ];

        foreach ($config_files as $file => $info) {
            if (stripos($request_uri, $file) !== false) {
                $score += $info['score'];
                $factors[] = 'Config access: ' . $file;
                break;
            }
        }

        return $score;
    }

    private function detect_php_file_access(string $request_uri, array &$factors): int {
        $score = 0;

        if (!preg_match('#\.php($|\?)#i', $request_uri)) return $score;

        $malicious_php_files = [
            'phpinfo.php'    => 8,
            'shell.php'      => 10,
            'cmd.php'        => 10,
            'c99.php'        => 10,
            'r57.php'        => 10,
            'wso.php'        => 10,
        ];

        $filename = basename(wp_parse_url($request_uri, PHP_URL_PATH) ?: '');

        foreach ($malicious_php_files as $file => $file_score) {
            if (stripos($filename, $file) !== false) {
                $score += $file_score;
                $factors[] = 'Malicious PHP file: ' . $file;
                return $score;
            }
        }

        return $score;
    }

    private function detect_wp_admin_attacks(string $request_uri, array &$factors): int {
        $score = 0;

        if (strpos($request_uri, '/wp-admin/') === false && strpos($request_uri, '/wp-login.php') === false) {
            return $score;
        }

        $suspicious_patterns = [
            '/wp-admin/install.php'    => ['score' => 9, 'reason' => 'Installation attempt'],
            '/wp-admin/setup-config.php' => ['score' => 9, 'reason' => 'Setup attempt'],
        ];

        foreach ($suspicious_patterns as $pattern => $info) {
            if (strpos($request_uri, $pattern) !== false) {
                $score += $info['score'];
                $factors[] = 'Admin access: ' . $info['reason'];
                break;
            }
        }

        return $score;
    }

    private function detect_malicious_parameters(array $params, array &$factors, string $method = 'GET'): int {
        if (empty($params)) return 0;

        $score = 0;
        if (!is_array($params)) return $score;

        $cache_key = 'scan_' . md5((string) wp_json_encode($params));

        if (isset(self::$scan_cache[$cache_key])) {
            return self::$scan_cache[$cache_key];
        }

        if (count(self::$scan_cache) > self::MAX_CACHE_SIZE) {
            array_shift(self::$scan_cache);
        }

        $skip_params = [
            'content', 'post_content', 'comment', 'message', 'description',
            'bio', 'excerpt', 'title', 'name', 'subject', 'body',
        ];

        $malicious_patterns = $this->get_malicious_patterns();

        foreach ($params as $key => $value) {
            if (in_array(strtolower((string)$key), $skip_params, true)) {
                continue;
            }

            if (is_array($value)) {
                $score += $this->detect_malicious_parameters($value, $factors, $method);
            } elseif (is_string($value)) {
                $length = strlen($value);

                if ($length > self::MAX_PARAM_LENGTH) {
                    $score += 3;
                    $factors[] = sprintf(esc_html__('Excessive length in %s parameter', 'botfend-anti-bot-firewall'), esc_html($key));
                    continue;
                }

                if ($length > 5) {
                    foreach ($malicious_patterns as $pattern => $pattern_score) {
                        if (preg_match($pattern, $value)) {
                            $score += $pattern_score;
                            $factors[] = sprintf(esc_html__('Suspicious %s parameter: %s', 'botfend-anti-bot-firewall'), $method, esc_html($key));
                            break;
                        }
                    }
                }
            }
        }

        self::$scan_cache[$cache_key] = $score;
        return $score;
    }

    public function monitor_admin_ajax(): void {
        if (defined('DOING_AJAX') && DOING_AJAX) {
            $ip = $this->utilities->get_client_ip();

            if ($this->should_bypass_protection($ip)) return;

            $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';

            if ($action === 'heartbeat' && $this->is_ajax_attack($ip, $action)) {
                $this->handle_wp_attack(
                    $ip, 2, ['Suspicious AJAX action: ' . $action],
                    isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '',
                    isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
                );
            }
        }
    }

    private function is_ajax_attack(string $ip, string $action): bool {
        $transient_key = 'botfend_ajax_' . md5($ip . '|' . $action);
        $count         = (int) get_transient($transient_key);

        $count++;
        set_transient($transient_key, $count, MINUTE_IN_SECONDS);

        return $count > 50;
    }

    public function track_login_failure(string $username): void {
        $ip = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip)) return;

        $sanitized_username = sanitize_user($username);
        if (empty($sanitized_username)) return;

        if (in_array(strtolower($sanitized_username), $this->allowed_usernames, true)) return;

        $window = $this->get_login_failure_window();
        $max_failures = $this->get_max_login_failures();
        
        $key = 'botfend_login_failures_' . md5($ip);
        $failures = get_transient($key) ?: [];
        
        $failures[] = time();
        $failures = array_filter($failures, function($timestamp) use ($window) {
            return $timestamp > (time() - $window);
        });
        
        set_transient($key, $failures, $window);
        
        $score = 0;
        $factors = ['Login failure: ' . $sanitized_username];

        $username_lower = strtolower($sanitized_username);
        if (isset($this->login_attack_patterns[$username_lower])) {
            $score += $this->login_attack_patterns[$username_lower]['score'];
            $factors[] = 'Common username attempt';
        }

        if (count($failures) >= $max_failures) {
            $score += 5;
            $factors[] = 'Multiple failures within window';
        }

        $should_allow = $this->apply_login_rate_limit($ip, $sanitized_username);

        if (!$should_allow || $score >= 5) {
            $this->handle_wp_attack(
                $ip, $score, $factors,
                isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '',
                isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            );
        }
    }
    
    /**
     * Check login brute force attempts
     */
    private function check_login_brute_force(string $ip): void {
        if ($this->should_bypass_protection($ip)) {
            return;
        }

        $transient_key = 'botfend_login_attempts_' . md5($ip);
        $attempts      = (int) get_transient($transient_key);
        $max_attempts  = $this->config->get_int('max_login_attempts', 10);

        if ($attempts >= $max_attempts) {
            $this->handle_wp_attack(
                $ip,
                9,
                ['Brute force login attempt'],
                'wp-login.php',
                isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            );
        }
    }

    private function apply_login_rate_limit(string $ip, string $username): bool {
        $transient_key = 'botfend_login_attempts_' . md5($ip);
        $attempts      = (int) get_transient($transient_key);

        if (0 === $attempts) {
            set_transient($transient_key, 1, 15 * MINUTE_IN_SECONDS);
            return true;
        }

        $max_attempts = $this->config->get_int('max_login_attempts', 10);

        if ($attempts >= $max_attempts) {
            $duration = $this->get_brute_force_block_duration();
            $this->database->block_ip(
                $ip, 'brute_force',
                sprintf(esc_html__('Too many login attempts: %s', 'botfend-anti-bot-firewall'), esc_html($username)),
                9, $duration, $this->utilities->get_country($ip)
            );
            return false;
        }

        set_transient($transient_key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
        return true;
    }

    public function check_login_attempts($user, string $username, string $password) {
        $ip = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip)) return $user;

        $transient_key = 'botfend_login_attempts_' . md5($ip);
        $attempts      = (int) get_transient($transient_key);
        $max_attempts  = $this->config->get_int('max_login_attempts', 10);

        if ($attempts >= $max_attempts) {
            return new WP_Error(
                'login_blocked',
                esc_html__('Too many login attempts. Please try again later.', 'botfend-anti-bot-firewall')
            );
        }

        return $user;
    }

    public function rest_api_protection($result) {
        if (!empty($result)) return $result;

        $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $ip          = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip) || is_user_logged_in()) return $result;

        foreach ($this->rest_attack_patterns as $pattern => $info) {
            if (strpos($request_uri, $pattern) !== false) {
                if ($this->config->get_bool('restrict_rest_api', true)) {
                    return new WP_Error(
                        'rest_forbidden',
                        esc_html__('REST API access restricted.', 'botfend-anti-bot-firewall'),
                        ['status' => 403]
                    );
                }
                break;
            }
        }

        return $result;
    }

    public function monitor_rest_api_calls(): void {
        $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $ip          = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip) || is_user_logged_in()) return;

        foreach ($this->rest_attack_patterns as $pattern => $info) {
            if (strpos($request_uri, $pattern) !== false) {
                $this->handle_wp_attack(
                    $ip, $info['score'], ['REST API access: ' . $pattern], $request_uri,
                    isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
                );
                break;
            }
        }
    }

    public function monitor_xmlrpc_calls(string $method): void {
        if (isset($this->xmlrpc_attack_patterns[$method])) {
            $ip = $this->utilities->get_client_ip();
            $pattern = $this->xmlrpc_attack_patterns[$method];

            if ($this->should_bypass_protection($ip)) return;

            $this->handle_wp_attack(
                $ip, $pattern['score'], ['XML-RPC: ' . $method], 'xmlrpc.php',
                isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            );
        }
    }

    public function protect_login_page(): void {
        $ip = $this->utilities->get_client_ip();

        if ($this->should_bypass_protection($ip)) return;

        if ($this->database->is_ip_blocked($ip)) {
            $this->send_security_response(403, 'IP address blocked');
        }

        $this->check_login_brute_force($ip);
    }

    public function block_author_enumeration(array $vars): array {
        if ($this->config->get_bool('block_author_scan', true)) {
            $vars = array_diff($vars, ['author']);
        }
        return $vars;
    }

    public function prevent_author_scans(): void {
        if (!$this->config->get_bool('block_author_scan', true)) return;

        $ip = $this->utilities->get_client_ip();
        if ($this->should_bypass_protection($ip)) return;

        if (is_author() || (isset($_GET['author']) && is_numeric($_GET['author']))) {
            $this->handle_wp_attack(
                $ip, 6, ['Author enumeration attempt'],
                isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '',
                isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            );
        }
    }

    public function prevent_pingback_attacks(): void {
        if (!is_singular() || !$this->config->get_bool('disable_pingbacks', false)) return;

        $ip = $this->utilities->get_client_ip();
        if ($this->should_bypass_protection($ip)) return;

        if (isset($_GET['tb']) || isset($_GET['pingback'])) {
            $this->handle_wp_attack(
                $ip, 5, ['Pingback attack attempt'],
                isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '',
                isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            );
        }
    }

    public function add_security_headers(): void {
        if (!headers_sent() && $this->config->get_bool('add_security_headers', true)) {
            header('X-Content-Type-Options: nosniff');
            header('X-Frame-Options: SAMEORIGIN');
            header('Referrer-Policy: strict-origin-when-cross-origin');
            header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
            header('X-XSS-Protection: 1; mode=block');

            if (is_ssl()) {
                header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
            }
        }
    }

    /**
     * Ensure execution halts after a block is triggered.
     */
    private function handle_wp_attack(string $ip, int $score, array $factors, string $request_uri, string $user_agent): void {
        $log_only = $this->config->get_bool('log_only', false);
        $country = $this->utilities->get_country($ip);
        $threshold = $this->config->get_int('wp_protection_threshold', 10);
        
        $blocking_enabled = $this->is_attack_blocking_enabled();
        
        $should_block = !$log_only && $score >= $threshold && $blocking_enabled;
        $should_block = apply_filters('botfend_should_block_attack', $should_block, $ip, $score, $factors);

        $reason_str = implode('; ', $factors);

        // We MUST log the request first before killing the script
        $this->database->log_request(
            $ip, $score, 'wordpress_security', $factors, $user_agent, false, $country
        );

        if ($should_block) {
            $is_brute_force = in_array('brute_force', array_map('strtolower', $factors)) ||
                              in_array('Multiple failures within window', $factors);
            
            $duration = $is_brute_force 
                ? $this->get_brute_force_block_duration() 
                : $this->get_attack_block_duration();
            
            // 1. Write the block to the database
            $this->database->block_ip($ip, 'wordpress_security', $reason_str, $score, $duration, $country);
            
            // 2. Trigger Firewall sync to stop future requests at the server level
            do_action('botfend_ip_blocked', $ip, 'wordpress_security', $score, $reason_str);

            // 3.  Halt the execution of the current thread!
            $this->send_security_response(403, esc_html__('Security Violation: Your access has been restricted.', 'botfend-anti-bot-firewall'));
        }
    }

    private function send_security_response(int $code, string $message): void {
        status_header($code);
        nocache_headers();

        wp_die(
            esc_html($message),
            esc_html__('Security Alert', 'botfend-anti-bot-firewall'),
            ['response' => $code]
        );
    }

    public function get_protection_status(): array {
        return [
            'enabled'                        => $this->is_enabled(),
            'attack_blocking_enabled'        => $this->is_attack_blocking_enabled(),
            'attack_block_duration'          => $this->get_attack_block_duration(),
            'brute_force_block_duration'     => $this->get_brute_force_block_duration(),
            'max_login_failures'             => $this->get_max_login_failures(),
            'login_failure_window'           => $this->get_login_failure_window(),
            'xmlrpc_disabled'                => $this->config->get_bool('disable_xmlrpc', false),
            'rest_api_restricted'            => $this->config->get_bool('restrict_rest_api', true),
            'author_scan_blocked'            => $this->config->get_bool('block_author_scan', true),
            'pingbacks_disabled'             => $this->config->get_bool('disable_pingbacks', false),
            'comment_protection_enabled'     => $this->config->get_bool('enable_comment_protection', true),
            'max_login_attempts'             => $this->config->get_int('max_login_attempts', 10),
            'wp_protection_threshold'        => $this->config->get_int('wp_protection_threshold', 10),
            'enable_country_whitelist'       => $this->config->get_bool('enable_country_whitelist', false),
            'whitelist_countries'            => $this->config->get_array('whitelist_countries', []),
            'whitelist_max_score'            => $this->config->get_int('whitelist_max_score', 8),
            'wpcontent_protection_enabled'   => $this->config->get_bool('enable_wpcontent_protection', false),
        ];
    }

    public static function clear_country_cache(): void {
        self::$country_whitelist_cache = [];
    }
    
    public static function clear_verified_crawler_cache(): void {
        self::$verified_crawler_cache = [];
    }

    public function get_cache_stats(): array {
        return [
            'scan_cache_size' => count(self::$scan_cache),
            'max_scan_cache' => self::MAX_CACHE_SIZE,
            'country_cache_size' => count(self::$country_whitelist_cache),
            'max_country_cache' => self::MAX_COUNTRY_CACHE,
            'verified_crawler_cache_size' => count(self::$verified_crawler_cache),
            'max_verified_crawler_cache' => self::MAX_VERIFIED_CRAWLER_CACHE,
        ];
    }
}