<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

class Vulnity_Mitigation_Manager {

    const BLOCK_TRANSIENT_INDEX = 'vulnity_block_transients';
    const BLOCK_TRANSIENT_INDEX_LIMIT = 500;

    private static $instance = null;
    private $blocked_ips_key = 'vulnity_blocked_ips';
    private $pending_block_notices_key = 'vulnity_pending_block_notices';
    private $sync_interval = 86400;
    private $firewall_manager;
    private $generic_alert_endpoint = VULNITY_BASE_URL . '/generic-alert';
    
    private static $blocked_ips_cache = null;
    private static $cache_loaded = false;
    private static $blocking_executed = false;
    
    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public static function early_blocking_check() {
        if (self::$blocking_executed) {
            return;
        }
        
        self::$blocking_executed = true;
        
        $ip = self::get_client_ip_static();
        
        // Never block localhost
        if (in_array($ip, array('127.0.0.1', '::1', 'localhost'))) {
            return;
        }
        
        // Check whitelist
        $whitelist = get_option('vulnity_ip_whitelist', array());
        if (in_array($ip, $whitelist)) {
            return;
        }
        
        if (!self::$cache_loaded) {
            $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
            $cached_block = get_transient($transient_key);
            
            if ($cached_block !== false) {
                if ($cached_block === 'blocked') {
                    self::execute_block_static($ip);
                }
                return;
            }
            
            self::$blocked_ips_cache = get_option('vulnity_blocked_ips', array());
            self::$cache_loaded = true;
        }
        
        if (isset(self::$blocked_ips_cache[$ip])) {
            $block_data = self::$blocked_ips_cache[$ip];

            if (self::is_block_active_static($block_data)) {
                $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
                $ttl = self::get_block_cache_ttl($block_data);
                if ($ttl > 0) {
                    set_transient($transient_key, 'blocked', $ttl);
                    self::remember_block_transient($transient_key);
                }
                self::execute_block_static($ip, $block_data);
            }
        } else {
            $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
            set_transient($transient_key, 'clean', 300);
        }
    }
    
    private static function parse_expiration_timestamp($value) {
        if (empty($value)) {
            return null;
        }

        if (is_numeric($value)) {
            return (int) $value;
        }

        if (!is_string($value)) {
            return null;
        }

        if (function_exists('wp_timezone')) {
            $timezone = wp_timezone();
            if ($timezone instanceof DateTimeZone) {
                $dt = date_create($value, $timezone);
                if ($dt instanceof DateTimeInterface) {
                    return $dt->getTimestamp();
                }
            }
        }

        $timestamp = strtotime($value);
        return $timestamp ? (int) $timestamp : null;
    }

    private static function is_block_active_static($block_data) {
        if (isset($block_data['permanent']) && $block_data['permanent']) {
            return true;
        }
        
        if (!empty($block_data['blocked_until'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['blocked_until']);
            return $expires_at && $expires_at > time();
        }
        
        if (!empty($block_data['expires_at'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['expires_at']);
            return $expires_at && $expires_at > time();
        }
        
        return false;
    }

    private static function get_block_cache_ttl($block_data) {
        if (!is_array($block_data)) {
            return 300;
        }

        if (isset($block_data['permanent']) && $block_data['permanent']) {
            return 300;
        }

        $expires_at = null;

        if (!empty($block_data['blocked_until'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['blocked_until']);
        } elseif (!empty($block_data['expires_at'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['expires_at']);
        }

        if ($expires_at) {
            $remaining = $expires_at - time();
            if ($remaining <= 0) {
                return 0;
            }
            return min(300, $remaining);
        }

        return 300;
    }
    
    private static function execute_block_static($ip, $block_data = null) {
        if ($block_data === null) {
            if (self::$blocked_ips_cache !== null && isset(self::$blocked_ips_cache[$ip])) {
                $block_data = self::$blocked_ips_cache[$ip];
            } else {
                $block_data = array('reason' => 'Security Block', 'blocked_at' => current_time('mysql'), 'permanent' => true);
            }
        }

        // Ensure block_data has required fields
        if (!isset($block_data['reason'])) {
            $block_data['reason'] = 'Security Block';
        }
        if (!isset($block_data['blocked_at'])) {
            $block_data['blocked_at'] = current_time('mysql');
        }

        if (!headers_sent()) {
            header('HTTP/1.1 403 Forbidden');
            header('X-Blocked-By: Vulnity Security');
            header('Cache-Control: no-cache, no-store, must-revalidate');
        }

        vulnity_log('[Vulnity] Blocked IP: ' . $ip); 

        $reason = ucwords(str_replace('_', ' ', $block_data['reason']));
        $blocked_timestamp = strtotime($block_data['blocked_at']);
        if (false === $blocked_timestamp) {
            $blocked_timestamp = current_time('timestamp');
        }
        $blocked_date = wp_date('F j, Y g:i A', $blocked_timestamp);

        $logo_url = plugins_url('../assets/Logo_Vulnity.png', __FILE__);
        $style_url = plugins_url('../assets/block-page.css', __FILE__);
        $style_markup = self::get_block_page_styles_markup($style_url);
        $status_section = '';

        if (isset($block_data['permanent']) && $block_data['permanent']) {
            $status_section = '<div class="d"><div class="l">Status</div><div class="v">Blocked</div></div>';
        } elseif (!empty($block_data['blocked_until'])) {
            $expires = self::parse_expiration_timestamp($block_data['blocked_until']);
            if ($expires && $expires > time()) {
                $status_section = '<div class="d"><div class="l">Status</div><div class="v">Blocked</div></div>';
            } else {
                $status_section = '<div class="d"><div class="l">Status</div><div class="v">Expired</div></div>';
            }
        }

        $reference = esc_html(substr(md5($ip . $block_data['blocked_at']), 0, 8));
        $ipEscaped = esc_html($ip);
        $reasonEscaped = esc_html($reason);
        $blockedDateEscaped = esc_html($blocked_date);
        $logoUrlEscaped = esc_url($logo_url);
        $statusSectionSafe = wp_kses(
            $status_section,
            array(
                'div' => array(
                    'class' => true,
                ),
            )
        );

        $html_template = implode('', array(
            '<!DOCTYPE html>',
            '<html>',
            '<head>',
            '<meta charset="UTF-8">',
            '<meta name="viewport" content="width=device-width,initial-scale=1">',
            '<title>Access Denied</title>',
            '%1$s',
            '</head>',
            '<body>',
            '<div class="c">',
            '    <div class="logo"><img src="%2$s" alt="Vulnity Security Logo"></div>',
            '    <h1>Access Denied</h1>',
            '    <div class="st">Your access has been blocked by Vulnity Security</div>',
            '    <div class="b">',
            '        <div class="d"><div class="l">Your IP Address</div><div class="v">%3$s</div></div>',
            '        <div class="d"><div class="l">Block Reason</div><div class="v">%4$s</div></div>',
            '        <div class="d"><div class="l">Blocked Since</div><div class="v">%5$s</div></div>',
            '        %6$s',
            '    </div>',
            '    <div class="f"><strong>Protected by Vulnity Security</strong><br>Reference: %7$s</div>',
            '</div>',
            '</body>',
            '</html>'
        ));

        $html = sprintf(
            $html_template,
            $style_markup,
            $logoUrlEscaped,
            $ipEscaped,
            $reasonEscaped,
            $blockedDateEscaped,
            $statusSectionSafe,
            $reference
        );

        echo wp_kses($html, self::get_block_page_allowed_tags());

        exit;
    }

    private static function get_client_ip_static() {
        return self::resolve_client_ip();
    }
    
    private function __construct() {
        $this->firewall_manager = Vulnity_Firewall_Manager::get_instance();

        add_action('vulnity_sync_mitigation_config', array($this, 'sync_mitigation_config'));

        if (!wp_next_scheduled('vulnity_sync_mitigation_config')) {
            wp_schedule_event(time(), 'daily', 'vulnity_sync_mitigation_config');
        }

        add_action('vulnity_alert_created', array($this, 'check_auto_block'), 10, 2);
        add_action('vulnity_alert_sent', array($this, 'handle_alert_sent'), 10, 2);

        add_action('wp_ajax_vulnity_unblock_ip', array($this, 'ajax_unblock_ip'));
        add_action('wp_ajax_vulnity_bulk_unblock_ips', array($this, 'ajax_bulk_unblock_ips'));
        add_action('wp_ajax_vulnity_block_ip', array($this, 'ajax_block_ip'));
        add_action('wp_ajax_vulnity_sync_mitigation', array($this, 'ajax_sync_mitigation'));
        add_action('wp_ajax_vulnity_save_suppress_blocked_alerts', array($this, 'ajax_save_suppress_blocked_alerts'));

        add_action('vulnity_cleanup', array($this, 'cleanup'));

        add_action('plugins_loaded', array(__CLASS__, 'early_blocking_check'), 0);

        $this->firewall_manager->sync_from_options();
    }

    private function verify_admin_ajax_request($capability = 'manage_options') {
        // Use `false` to prevent `check_ajax_referer` from exiting; we send JSON errors and return early ourselves.
        $verified = check_ajax_referer('vulnity_nonce', 'nonce', false);

        if (!$verified) {
            wp_send_json_error('Security check failed');
            return false;
        }

        if (!current_user_can($capability)) {
            wp_send_json_error('Security check failed');
            return false;
        }

        return true;
    }

    private function fast_ip_block_check() {
        $ip = $this->get_client_ip();
        
        if (!self::$cache_loaded) {
            $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
            $cached_block = get_transient($transient_key);
            
            if ($cached_block !== false) {
                if ($cached_block === 'blocked') {
                    $this->execute_block($ip);
                }
                return;
            }
            
            self::$blocked_ips_cache = get_option($this->blocked_ips_key, array());
            self::$cache_loaded = true;
        }
        
        if (isset(self::$blocked_ips_cache[$ip])) {
            $block_data = self::$blocked_ips_cache[$ip];

            if ($this->is_block_active($block_data)) {
                $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
                set_transient($transient_key, 'blocked', 300);
                self::remember_block_transient($transient_key);
                $this->execute_block($ip, $block_data);
            }
        } else {
            $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
            set_transient($transient_key, 'clean', 300);
        }
    }
    
    private function execute_block($ip, $block_data = null) {
        if ($block_data === null) {
            if (self::$blocked_ips_cache !== null && isset(self::$blocked_ips_cache[$ip])) {
                $block_data = self::$blocked_ips_cache[$ip];
            } else {
                $block_data = array('reason' => 'Security Block', 'blocked_at' => current_time('mysql'), 'permanent' => true);
            }
        }

        // Ensure block_data has required fields
        if (!isset($block_data['reason'])) {
            $block_data['reason'] = 'Security Block';
        }
        if (!isset($block_data['blocked_at'])) {
            $block_data['blocked_at'] = current_time('mysql');
        }

        if (!headers_sent()) {
            header('HTTP/1.1 403 Forbidden');
            header('X-Blocked-By: Vulnity Security');
            header('Cache-Control: no-cache, no-store, must-revalidate');
        }

        vulnity_log('[Vulnity] Blocked IP: ' . $ip); 

        $reason = ucwords(str_replace('_', ' ', $block_data['reason']));
        $blocked_timestamp = strtotime($block_data['blocked_at']);
        if (false === $blocked_timestamp) {
            $blocked_timestamp = current_time('timestamp');
        }
        $blocked_date = wp_date('F j, Y g:i A', $blocked_timestamp);

        $logo_url = plugins_url('../assets/Logo_Vulnity.png', __FILE__);
        $style_url = plugins_url('../assets/block-page.css', __FILE__);
        $style_markup = self::get_block_page_styles_markup($style_url);
        $status_section = '';

        if (isset($block_data['permanent']) && $block_data['permanent']) {
            $status_section = '<div class="d"><div class="l">Status</div><div class="v">Blocked</div></div>';
        } elseif (!empty($block_data['blocked_until'])) {
            $expires = self::parse_expiration_timestamp($block_data['blocked_until']);
            if ($expires && $expires > time()) {
                $status_section = '<div class="d"><div class="l">Status</div><div class="v">Blocked</div></div>';
            } else {
                $status_section = '<div class="d"><div class="l">Status</div><div class="v">Expired</div></div>';
            }
        }

        $reference = esc_html(substr(md5($ip . $block_data['blocked_at']), 0, 8));
        $ipEscaped = esc_html($ip);
        $reasonEscaped = esc_html($reason);
        $blockedDateEscaped = esc_html($blocked_date);
        $logoUrlEscaped = esc_url($logo_url);
        $statusSectionSafe = wp_kses(
            $status_section,
            array(
                'div' => array(
                    'class' => true,
                ),
            )
        );

        $html_template = implode('', array(
            '<!DOCTYPE html>',
            '<html>',
            '<head>',
            '<meta charset="UTF-8">',
            '<meta name="viewport" content="width=device-width,initial-scale=1">',
            '<title>Access Denied</title>',
            '%1$s',
            '</head>',
            '<body>',
            '<div class="c">',
            '    <div class="logo"><img src="%2$s" alt="Vulnity Security Logo"></div>',
            '    <h1>Access Denied</h1>',
            '    <div class="st">Your access has been blocked by Vulnity Security</div>',
            '    <div class="b">',
            '        <div class="d"><div class="l">Your IP Address</div><div class="v">%3$s</div></div>',
            '        <div class="d"><div class="l">Block Reason</div><div class="v">%4$s</div></div>',
            '        <div class="d"><div class="l">Blocked Since</div><div class="v">%5$s</div></div>',
            '        %6$s',
            '    </div>',
            '    <div class="f"><strong>Protected by Vulnity Security</strong><br>Reference: %7$s</div>',
            '</div>',
            '</body>',
            '</html>'
        ));

        $html = sprintf(
            $html_template,
            $style_markup,
            $logoUrlEscaped,
            $ipEscaped,
            $reasonEscaped,
            $blockedDateEscaped,
            $statusSectionSafe,
            $reference
        );

        echo wp_kses($html, self::get_block_page_allowed_tags());

        exit;
    }

    private function is_block_active($block_data) {
        if (isset($block_data['permanent']) && $block_data['permanent']) {
            return true;
        }
        
        if (!empty($block_data['blocked_until'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['blocked_until']);
            return $expires_at && $expires_at > time();
        }
        
        if (!empty($block_data['expires_at'])) {
            $expires_at = self::parse_expiration_timestamp($block_data['expires_at']);
            return $expires_at && $expires_at > time();
        }
        
        return false;
    }
    
    public function fetch_mitigation_config_v2() {
        $config = get_option('vulnity_config');
        
        if (empty($config['site_id']) || empty($config['token'])) {
            return false;
        }
        
        $endpoint = VULNITY_BASE_URL . '/mitigation-config';
        
        $headers = array(
            'Content-Type'    => 'application/json',
            'x-vulnity-token' => $config['token'],
            'x-site-id'       => $config['site_id'],
            'User-Agent'      => 'Vulnity-Plugin/' . VULNITY_VERSION,
        );
        
        $args = array(
            'headers' => $headers,
            'timeout' => 30,
            'sslverify' => true
        );
        
        $response = wp_remote_get($endpoint, $args);
        
        if (is_wp_error($response)) {
            return false;
        }
        
        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        
        if ($status_code !== 200) {
            return false;
        }
        
        $data = json_decode($body, true);
        
        if (!$data || !isset($data['success']) || !$data['success']) {
            return false;
        }
        
        return $data['data'];
    }
    
    public function sync_mitigation_config() {
        $plugin_config = get_option('vulnity_config');

        if (empty($plugin_config['site_id']) || empty($plugin_config['token'])) {
            return false;
        }

        // Build local state to send alongside the sync request (bidirectional in one call)
        $local_blocked = $this->get_blocked_ips();
        $blocked_ips_payload = array();
        foreach ($local_blocked as $ip => $data) {
            $source = isset($data['source']) ? $data['source'] : '';
            $type   = in_array($source, array('auto_block', 'siem_sync', 'siem_push', 'vulnity_blacklist'), true)
                      ? 'automatic'
                      : 'manual';

            // Resolve expiration from either expires_at or blocked_until
            $expires = null;
            if (!empty($data['expires_at'])) {
                $expires = $data['expires_at'];
            } elseif (!empty($data['blocked_until'])) {
                $expires = $data['blocked_until'];
            }

            $blocked_ips_payload[] = array(
                'ip'         => $ip,
                'reason'     => isset($data['reason'])     ? $data['reason']     : '',
                'type'       => $type,
                'blocked_at' => isset($data['blocked_at']) ? $data['blocked_at'] : '',
                'expires_at' => $expires,
            );
        }

        $whitelist_ips = array_values(get_option('vulnity_ip_whitelist', array()));

        $body_json = wp_json_encode(array(
            'blocked_ips'   => $blocked_ips_payload,
            'whitelist_ips' => $whitelist_ips,
        ));

        $response = wp_remote_post(
            VULNITY_BASE_URL . '/mitigation-config',
            array(
                'method'  => 'POST',
                'headers' => array(
                    'Content-Type'    => 'application/json',
                    'x-vulnity-token' => $plugin_config['token'],
                    'x-site-id'       => $plugin_config['site_id'],
                ),
                'body'      => $body_json,
                'timeout'   => 30,
                'sslverify' => true,
            )
        );

        if (is_wp_error($response)) {
            vulnity_log('[Vulnity] sync_mitigation_config request failed: ' . $response->get_error_message());
            return false;
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $body        = wp_remote_retrieve_body($response);

        if ($status_code !== 200) {
            vulnity_log('[Vulnity] sync_mitigation_config HTTP ' . $status_code . ': ' . $body);
            return false;
        }

        $data = json_decode($body, true);

        if (!$data || empty($data['success']) || !isset($data['data'])) {
            vulnity_log('[Vulnity] sync_mitigation_config unexpected response: ' . $body);
            return false;
        }

        $config = $data['data'];

        // Apply SIEM's authoritative mitigation rules
        if (isset($config['mitigation_rules']) && is_array($config['mitigation_rules'])) {
            update_option('vulnity_mitigation_rules', $config['mitigation_rules']);
        }

        // Apply SIEM's whitelist (always keep localhost)
        if (isset($config['whitelist']) && is_array($config['whitelist'])) {
            $whitelist = $config['whitelist'];
            foreach (array('127.0.0.1', '::1') as $local_ip) {
                if (!in_array($local_ip, $whitelist)) {
                    $whitelist[] = $local_ip;
                }
            }
            update_option('vulnity_ip_whitelist', $whitelist);
        } else {
            update_option('vulnity_ip_whitelist', array('127.0.0.1', '::1'));
        }

        // Reconcile SIEM-managed blocked IPs (adds new, updates existing, removes unblocked)
        if (isset($config['blocked_ips']) && is_array($config['blocked_ips'])) {
            $this->reconcile_siem_blocked_ips($config['blocked_ips']);
        }

        // Apply global Vulnity blacklist if enabled
        if (!empty($config['vulnity_blacklist']['enabled']) && !empty($config['vulnity_blacklist']['ips'])) {
            $this->merge_vulnity_blacklist($config['vulnity_blacklist']['ips']);
        }

        $this->prune_expired_blocked_ips();
        $this->clear_all_block_cache();
        $this->firewall_manager->sync_from_options();

        update_option('vulnity_last_config_sync', current_time('mysql'));
        update_option('vulnity_last_mitigation_sync', array(
            'timestamp'    => current_time('mysql'),
            'status'       => 'success',
            'config_hash'  => md5(json_encode($config)),
            'last_updated' => isset($config['last_updated']) ? $config['last_updated'] : null,
        ));

        vulnity_log('[Vulnity] sync_mitigation_config completed successfully');
        return true;
    }

    private function push_local_mitigation_config() {
        $config = get_option('vulnity_config');

        if (empty($config['site_id']) || empty($config['token'])) {
            return false;
        }

        // Format blocked IPs: internal keyed-by-IP array → list of objects (expired entries excluded)
        $local_blocked = $this->get_blocked_ips();
        $blocked_ips   = array();
        foreach ($local_blocked as $ip => $data) {
            $source = isset($data['source']) ? $data['source'] : '';

            // Classify as manual or automatic for the SIEM's display taxonomy
            $type = in_array($source, array('auto_block', 'siem_sync', 'siem_push', 'vulnity_blacklist'), true)
                    ? 'automatic'
                    : 'manual';

            // Resolve expiration from either expires_at or blocked_until (auto_block uses blocked_until)
            $expires = null;
            if (!empty($data['expires_at'])) {
                $expires = $data['expires_at'];
            } elseif (!empty($data['blocked_until'])) {
                $expires = $data['blocked_until'];
            }

            $blocked_ips[] = array(
                'ip'         => $ip,
                'reason'     => isset($data['reason'])     ? $data['reason']     : '',
                'type'       => $type,
                'blocked_at' => isset($data['blocked_at']) ? $data['blocked_at'] : '',
                'expires_at' => $expires,
            );
        }

        $whitelist_ips = array_values(get_option('vulnity_ip_whitelist', array()));

        $payload = array(
            'blocked_ips'   => $blocked_ips,
            'whitelist_ips' => $whitelist_ips,
        );

        $response = wp_remote_post(
            VULNITY_BASE_URL . '/mitigation-config',
            array(
                'method'  => 'POST',
                'headers' => array(
                    'Content-Type'    => 'application/json',
                    'x-vulnity-token' => $config['token'],
                    'x-site-id'       => $config['site_id'],
                ),
                'body'      => wp_json_encode($payload),
                'timeout'   => 30,
                'sslverify' => true,
            )
        );

        if (is_wp_error($response)) {
            vulnity_log('[Vulnity] push_local_mitigation_config failed: ' . $response->get_error_message());
            return false;
        }

        $status = wp_remote_retrieve_response_code($response);
        vulnity_log('[Vulnity] push_local_mitigation_config HTTP ' . $status);
        return ($status >= 200 && $status < 300);
    }

    // -----------------------------------------------------------------------
    // Public action handlers called by REST /receive-mitigation
    // -----------------------------------------------------------------------

    public function apply_push_rules($data) {
        if (isset($data['mitigation_rules']) && is_array($data['mitigation_rules'])) {
            update_option('vulnity_mitigation_rules', $data['mitigation_rules']);
        }

        if (isset($data['whitelist']) && is_array($data['whitelist'])) {
            $whitelist = $data['whitelist'];
            foreach (array('127.0.0.1', '::1') as $local_ip) {
                if (!in_array($local_ip, $whitelist)) {
                    $whitelist[] = $local_ip;
                }
            }
            update_option('vulnity_ip_whitelist', $whitelist);
        }

        if (isset($data['blocked_ips']) && is_array($data['blocked_ips'])) {
            $this->merge_blocked_ips($data['blocked_ips']);
        }

        $this->clear_all_block_cache();
        $this->firewall_manager->sync_from_options();

        return true;
    }

    public function apply_push_block($ip, $reason, $duration, $expires_at) {
        if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }

        $blocked_ips        = get_option($this->blocked_ips_key, array());
        $blocked_ips[$ip]   = array(
            'reason'     => $reason,
            'blocked_at' => current_time('mysql'),
            'expires_at' => $expires_at,
            'duration'   => $duration,
            'source'     => 'siem_push',
            'permanent'  => empty($expires_at),
        );

        update_option($this->blocked_ips_key, $blocked_ips);
        self::$blocked_ips_cache = $blocked_ips;
        self::$cache_loaded      = true;
        $this->clear_all_block_cache();
        $this->firewall_manager->sync_from_options();

        return true;
    }

    public function apply_push_unblock($ip) {
        if (empty($ip)) {
            return false;
        }

        $blocked_ips = get_option($this->blocked_ips_key, array());

        if (!isset($blocked_ips[$ip])) {
            return true; // already not blocked — treat as success
        }

        unset($blocked_ips[$ip]);
        update_option($this->blocked_ips_key, $blocked_ips);
        self::$blocked_ips_cache = $blocked_ips;
        self::$cache_loaded      = true;
        $this->clear_all_block_cache();
        $this->firewall_manager->sync_from_options();

        return true;
    }

    public function prune_expired_blocked_ips() {
        $blocked_ips    = get_option($this->blocked_ips_key, array());
        $original_count = count($blocked_ips);

        foreach ($blocked_ips as $ip => $data) {
            if (!self::is_block_active_static($data)) {
                unset($blocked_ips[$ip]);
                vulnity_log('[Vulnity] Pruned expired block for IP: ' . $ip);
            }
        }

        $removed = $original_count - count($blocked_ips);

        if ($removed > 0) {
            update_option($this->blocked_ips_key, $blocked_ips);
            self::$blocked_ips_cache = $blocked_ips;
            self::$cache_loaded      = true;
            $this->clear_all_block_cache();
            $this->firewall_manager->sync_from_options();
        }

        return $removed;
    }

    public function apply_whitelist_add($ip) {
        if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }

        $whitelist = get_option('vulnity_ip_whitelist', array());

        if (!in_array($ip, $whitelist)) {
            $whitelist[] = $ip;
            update_option('vulnity_ip_whitelist', $whitelist);
            $this->firewall_manager->sync_from_options();
        }

        return true;
    }

    public function apply_whitelist_remove($ip) {
        if (empty($ip)) {
            return false;
        }

        // Never remove localhost entries
        if (in_array($ip, array('127.0.0.1', '::1'))) {
            return false;
        }

        $whitelist = get_option('vulnity_ip_whitelist', array());
        $whitelist = array_values(array_filter($whitelist, function($w) use ($ip) {
            return $w !== $ip;
        }));

        update_option('vulnity_ip_whitelist', $whitelist);
        $this->firewall_manager->sync_from_options();

        return true;
    }

    private function merge_blocked_ips($new_blocked_ips) {
        $local_blocked_ips = get_option($this->blocked_ips_key, array());
        $changed = false;

        foreach ($new_blocked_ips as $block_data) {
            if (!is_array($block_data)) {
                continue;
            }

            // Support both ip_address (SIEM format) and ip (alternate format)
            if (!empty($block_data['ip_address'])) {
                $ip = $block_data['ip_address'];
            } elseif (!empty($block_data['ip'])) {
                $ip = $block_data['ip'];
            } else {
                continue;
            }

            if (!filter_var($ip, FILTER_VALIDATE_IP)) {
                continue;
            }

            // Don't overwrite active local blocks (auto_block, manual_block) with SIEM data
            $existing_source = isset($local_blocked_ips[$ip]['source']) ? $local_blocked_ips[$ip]['source'] : '';
            $is_local_block  = in_array($existing_source, array('auto_block', 'manual_block'), true);

            if (!isset($local_blocked_ips[$ip]) || !$is_local_block) {
                $local_blocked_ips[$ip] = array(
                    'reason'     => isset($block_data['reason'])     ? $block_data['reason']     : 'SIEM block',
                    'blocked_at' => current_time('mysql'),
                    'expires_at' => isset($block_data['expires_at']) ? $block_data['expires_at'] : null,
                    'duration'   => isset($block_data['duration'])   ? $block_data['duration']   : null,
                    'source'     => 'siem_sync',
                    'permanent'  => !isset($block_data['expires_at']) || empty($block_data['expires_at']),
                );
                $changed = true;
            }
        }

        if ($changed) {
            update_option($this->blocked_ips_key, $local_blocked_ips);
            self::$blocked_ips_cache = $local_blocked_ips;
            self::$cache_loaded      = true;
            $this->firewall_manager->sync_from_options();
        }
    }

    /**
     * Reconcile SIEM-managed blocked IPs with the authoritative list from SIEM.
     * Adds/updates SIEM-managed blocks and removes SIEM-managed blocks that are
     * no longer in the SIEM list (i.e. were unblocked from the SIEM dashboard).
     * Local blocks (auto_block, manual_block) are never touched.
     */
    private function reconcile_siem_blocked_ips($siem_blocked_ips) {
        $local_blocked_ips = get_option($this->blocked_ips_key, array());
        $changed           = false;

        // Build a map of IPs that SIEM says are blocked
        $siem_ip_map = array();
        foreach ($siem_blocked_ips as $block_data) {
            if (!is_array($block_data)) {
                continue;
            }
            if (!empty($block_data['ip_address'])) {
                $ip = $block_data['ip_address'];
            } elseif (!empty($block_data['ip'])) {
                $ip = $block_data['ip'];
            } else {
                continue;
            }
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                $siem_ip_map[$ip] = $block_data;
            }
        }

        // Add or update SIEM-managed blocks
        foreach ($siem_ip_map as $ip => $block_data) {
            $existing_source = isset($local_blocked_ips[$ip]['source']) ? $local_blocked_ips[$ip]['source'] : '';
            $is_local_block  = in_array($existing_source, array('auto_block', 'manual_block'), true);

            if (!isset($local_blocked_ips[$ip]) || !$is_local_block) {
                $local_blocked_ips[$ip] = array(
                    'reason'     => isset($block_data['reason'])     ? $block_data['reason']     : 'SIEM block',
                    'blocked_at' => current_time('mysql'),
                    'expires_at' => isset($block_data['expires_at']) ? $block_data['expires_at'] : null,
                    'duration'   => isset($block_data['duration'])   ? $block_data['duration']   : null,
                    'source'     => 'siem_sync',
                    'permanent'  => !isset($block_data['expires_at']) || empty($block_data['expires_at']),
                );
                $changed = true;
            }
        }

        // Remove SIEM-managed blocks that are no longer in the SIEM list
        foreach ($local_blocked_ips as $ip => $data) {
            $source = isset($data['source']) ? $data['source'] : '';
            if (in_array($source, array('siem_sync', 'siem_push'), true) && !isset($siem_ip_map[$ip])) {
                unset($local_blocked_ips[$ip]);
                $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
                delete_transient($transient_key);
                self::forget_block_transient($transient_key);
                $changed = true;
                vulnity_log('[Vulnity] Sync reconciled: removed SIEM-unblocked IP ' . $ip);
            }
        }

        if ($changed) {
            update_option($this->blocked_ips_key, $local_blocked_ips);
            self::$blocked_ips_cache = $local_blocked_ips;
            self::$cache_loaded      = true;
            $this->firewall_manager->sync_from_options();
        }
    }
    
    private function merge_vulnity_blacklist($blacklist_ips) {
        if (!is_array($blacklist_ips) || empty($blacklist_ips)) {
            return;
        }
        
        $local_blocked_ips = get_option($this->blocked_ips_key, array());
        $added_count = 0;
        
        foreach ($blacklist_ips as $ip_data) {
            if ( ! is_array( $ip_data ) || ! isset( $ip_data['ip_address'] ) ) {
                continue;
            }

            $ip = $ip_data['ip_address'];

            if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
                continue;
            }

            if (!isset($local_blocked_ips[$ip])) {
                $local_blocked_ips[$ip] = array(
                    'reason' => isset($ip_data['reason']) ? $ip_data['reason'] : 'Vulnity blacklist',
                    'blocked_at' => current_time('mysql'),
                    'source' => 'vulnity_blacklist',
                    'severity' => isset($ip_data['severity']) ? $ip_data['severity'] : 'high',
                    'category' => isset($ip_data['category']) ? $ip_data['category'] : 'malicious',
                    'permanent' => true
                );
                $added_count++;
            }
        }
        
        if ($added_count > 0) {
            update_option($this->blocked_ips_key, $local_blocked_ips);
            self::$blocked_ips_cache = $local_blocked_ips;
            self::$cache_loaded = true;
            $this->firewall_manager->sync_from_options();
        }
    }
    
    private function clear_all_block_cache() {
        $indexed_transients = self::get_block_transient_index();

        if (!empty($indexed_transients)) {
            foreach ($indexed_transients as $transient_key) {
                delete_transient($transient_key);
            }

            delete_option(self::BLOCK_TRANSIENT_INDEX);
        }

        self::$blocked_ips_cache = null;
        self::$cache_loaded = false;
    }
    
    public function check_auto_block($alert_id, $alert_data) {
        $rules = get_option('vulnity_mitigation_rules', array());
        
        if (empty($rules)) {
            return;
        }
        
        $ip = $this->extract_ip_from_alert($alert_data);
        
        if (!$ip) {
            return;
        }
        
        // Never block localhost
        if (in_array($ip, array('127.0.0.1', '::1', 'localhost'))) {
            return;
        }
        
        $whitelist = get_option('vulnity_ip_whitelist', array());
        
        if (in_array($ip, $whitelist)) {
            return;
        }
        
        $blocked_ips = get_option($this->blocked_ips_key, array());
        if (isset($blocked_ips[$ip]) && $this->is_block_active($blocked_ips[$ip])) {
            return;
        }
        
        $alert_type_normalized = $this->normalize_alert_type(isset($alert_data['type']) ? $alert_data['type'] : '');

        foreach ($rules as $rule) {
            if (!$rule['enabled']) {
                continue;
            }
            
            $alert_type_key = isset($rule['alertType']) ? $rule['alertType'] : (isset($rule['alert_type']) ? $rule['alert_type'] : '');
            
            $rule_alert_type = $this->normalize_alert_type($alert_type_key);

            if ($rule_alert_type !== $alert_type_normalized && $rule_alert_type !== 'all') {
                continue;
            }
            
            $min_severity = isset($rule['minSeverity']) ? $rule['minSeverity'] : (isset($rule['min_severity']) ? $rule['min_severity'] : 'low');
            $effective_severity = $this->resolve_alert_severity($alert_data);
            if (!$this->meets_severity_threshold($effective_severity, $min_severity)) {
                continue;
            }
            
            if ($this->should_block_ip($ip, $rule_alert_type === 'all' ? 'all' : $alert_type_normalized, $rule, $min_severity)) {
                $this->block_ip_locally($ip, $alert_data, $rule);
                break;
            }
        }
    }
    
    private function extract_ip_from_alert($alert_data) {
        if (isset($alert_data['details']['ip'])) {
            return $alert_data['details']['ip'];
        }
        
        if (isset($alert_data['details']['ip_address'])) {
            return $alert_data['details']['ip_address'];
        }
        
        if (isset($alert_data['details']['user_ip'])) {
            return $alert_data['details']['user_ip'];
        }
        
        if (isset($alert_data['details']['attacker_ip'])) {
            return $alert_data['details']['attacker_ip'];
        }
        
        return null;
    }
    
    private function should_block_ip($ip, $alert_type, $rule, $min_severity) {
        $threshold = isset($rule['threshold']) ? absint($rule['threshold']) : 5;
        $threshold = max(1, $threshold);

        $window_seconds = $this->resolve_rule_duration_seconds($rule, '1h');

        $counter = $this->increment_alert_counter($ip, $alert_type, $window_seconds);

        if ($counter >= $threshold) {
            return true;
        }

        // Fallback to historical scan for compatibility or when counters reset.
        $recent_alerts = $this->get_recent_alerts_for_ip($ip, $alert_type, $window_seconds, $min_severity);
        if (count($recent_alerts) >= $threshold) {
            return true;
        }
        
        if ($alert_type === 'php_file_uploaded' && $threshold === 1) {
            return true;
        }
        
        if ($alert_type === 'scanner_detected' && isset($rule['immediate_block']) && $rule['immediate_block']) {
            return true;
        }
        
        return false;
    }
    
    private function get_recent_alerts_for_ip($ip, $alert_type, $window_seconds, $min_severity) {
        $alerts = get_option('vulnity_alerts', array());

        // Performance limit: only check last 200 alerts to avoid timeouts
        if (is_array($alerts) && count($alerts) > 200) {
            $alerts = array_slice($alerts, 0, 200);
        }

        $recent_alerts = array();

        $window_seconds = max(1, (int) $window_seconds);
        $cutoff_time = time() - $window_seconds;

        foreach ($alerts as $alert) {
            if ($alert['type'] !== $alert_type && $alert_type !== 'all') {
                continue;
            }

            $alert_ip = $this->extract_ip_from_alert($alert);
            if ($alert_ip !== $ip) {
                continue;
            }

            $alert_time = strtotime($alert['timestamp']);
            if ($alert_time < $cutoff_time) {
                continue;
            }

            $effective_severity = $this->resolve_alert_severity($alert);
            if (!$this->meets_severity_threshold($effective_severity, $min_severity)) {
                continue;
            }

            $recent_alerts[] = $alert;
        }

        return $recent_alerts;
    }
    
    private function parse_duration($duration) {
        if (preg_match('/(\d+)([hmd])/', $duration, $matches)) {
            $value = intval($matches[1]);
            $unit = $matches[2];

            switch ($unit) {
                case 'h':
                    return $value * 3600;
                case 'd':
                    return $value * 86400;
                case 'm':
                    return $value * 60;
            }
        }

        return 3600;
    }

    private function normalize_alert_type($type) {
        $type = strtolower(trim((string) $type));

        if ($type === '') {
            return '';
        }

        $type = str_replace(array('-', ' '), '_', $type);

        $map = array(
            'suspicious_query_alert' => 'suspicious_query',
            'suspicious_queries' => 'suspicious_query',
            'scanner_detection' => 'scanner_detected',
            'brute_force_attack' => 'brute_force',
            'file_upload' => 'php_file_uploaded',
        );

        return isset($map[$type]) ? $map[$type] : $type;
    }

    private function normalize_catalog_key($value) {
        if (!is_scalar($value)) {
            return '';
        }

        $normalized = strtolower(trim((string) $value));
        if ($normalized === '') {
            return '';
        }

        $normalized = preg_replace('/[^a-z0-9]+/', '_', $normalized);

        return trim($normalized, '_');
    }

    private function get_severity_override_map() {
        static $severity_map = null;

        if ($severity_map !== null) {
            return $severity_map;
        }

        $catalog = array(
            'critical' => array(
                'privilege_escalation',
                'roles.privilege_escalation',
                'plugin_cve',
                'plugin.vuln_match',
                'config.exposed_backup_files',
                'core_integrity_breach',
                'file_security_alert',
                'php_upload',
                'php_file_uploaded',
            ),
            'high' => array(
                'auth.bruteforce',
                'bruteforce_ip',
                'brute_force_alert',
                'brute_force',
                'users.new_admin_created',
                'new_admin_created',
                'plugin.deactivated_security',
                'security_plugin_disabled',
                'core.update_available_critical',
                'core_security_update',
                'option.siteurl_or_home_changed',
                'siteurl_changed',
                'theme_cve',
                'suspicious_upload',
                'elevated_default_role',
                'user_management_alert',
                'file_editor_alert',
                'core_update_alert',
                'suspicious_query_alert',
                'suspicious_query',
            ),
            'medium' => array(
                'auth.success_from_new_ip',
                'login_new_ip',
                'bruteforce_user',
                'password_reset_spike',
                'plugin.installed_or_activated',
                'plugin_activated',
                'plugin_change',
                'plugin_change_alert',
                'file.editor_usage',
                'file_editor_used',
                'file.editor_enabled',
                'restapi.sensitive_route_access',
                'rest_access_no_auth',
                'theme_changed',
                'theme_change',
                'theme_change_alert',
                'user_registration_enabled',
                'email_spike',
                'directory_listing',
                'app_password_created',
                'plugin_outdated',
                'theme_outdated',
                'scanner_detected_alert',
                'scanner_detected',
            ),
            'low' => array(
                'admin_user_exists',
            ),
            'info' => array(
                'user_created',
                'user_deleted',
                'system_notification',
                'heartbeat',
                'site_paired',
                'site_unpaired',
            ),
        );

        $severity_map = array();

        foreach ($catalog as $severity => $types) {
            foreach ($types as $type_key) {
                $normalized = $this->normalize_catalog_key($type_key);
                if ($normalized !== '') {
                    $severity_map[$normalized] = $severity;
                }
            }
        }

        return $severity_map;
    }

    private function resolve_alert_severity($alert) {
        $default = isset($alert['severity']) ? strtolower((string) $alert['severity']) : '';
        if (!in_array($default, array('critical', 'high', 'medium', 'low', 'info'), true)) {
            $default = 'low';
        }

        $candidates = array();

        if (isset($alert['type'])) {
            $candidates[] = $alert['type'];
        }

        if (isset($alert['details']) && is_array($alert['details'])) {
            if (!empty($alert['details']['alert_type'])) {
                $candidates[] = $alert['details']['alert_type'];
            }

            if (!empty($alert['details']['action'])) {
                $candidates[] = $alert['details']['action'];
            }
        }

        $severity_map = $this->get_severity_override_map();

        foreach ($candidates as $candidate) {
            $normalized = $this->normalize_catalog_key($candidate);
            if ($normalized !== '' && isset($severity_map[$normalized])) {
                return $severity_map[$normalized];
            }
        }

        return $default;
    }

    private function resolve_rule_duration_seconds($rule, $fallback) {
        $duration = isset($rule['duration']) ? $rule['duration'] : $fallback;

        if ($duration === 'custom') {
            $minutes = isset($rule['customMinutes']) ? absint($rule['customMinutes']) : 0;
            $minutes = max(1, $minutes);
            return $minutes * 60;
        }

        $seconds = $this->parse_duration($duration);
        return max(1, (int) $seconds);
    }

    private function format_rule_duration_label($rule, $fallback) {
        $duration = isset($rule['duration']) ? $rule['duration'] : $fallback;

        if ($duration === 'custom') {
            $minutes = isset($rule['customMinutes']) ? absint($rule['customMinutes']) : 0;
            $minutes = max(1, $minutes);
            return $minutes === 1 ? '1 minuto' : $minutes . ' minutos';
        }

        return $this->format_duration_label($duration);
    }

    private function increment_alert_counter($ip, $alert_type, $window_seconds) {
        $counter_key = 'vulnity_alert_counter_' . md5($ip . '|' . $alert_type . '|' . (int) $window_seconds);
        $counter = get_transient($counter_key);
        $now = time();

        if (!is_array($counter) || empty($counter['first_seen']) || ($now - (int) $counter['first_seen']) > $window_seconds) {
            $counter = array(
                'count' => 1,
                'first_seen' => $now,
                'last_seen' => $now,
            );
        } else {
            $counter['count'] = isset($counter['count']) ? (int) $counter['count'] + 1 : 1;
            $counter['last_seen'] = $now;
        }

        set_transient($counter_key, $counter, $window_seconds + 60);

        return isset($counter['count']) ? (int) $counter['count'] : 1;
    }

    private function format_duration_label($duration) {
        if (empty($duration) || $duration === 'permanent') {
            return 'permanent';
        }

        $map = array(
            '1h' => '1 hour',
            '6h' => '6 hours',
            '24h' => '24 hours',
            '7d' => '7 days',
            '30d' => '30 days'
        );

        if (isset($map[$duration])) {
            return $map[$duration];
        }

        if (is_numeric($duration)) {
            return $duration . ' seconds';
        }

        return $duration;
    }
    
    private function meets_severity_threshold($alert_severity, $min_severity) {
        $severity_levels = array(
            'low' => 1,
            'medium' => 2,
            'high' => 3,
            'critical' => 4
        );
        
        $alert_level = isset($severity_levels[$alert_severity]) ? $severity_levels[$alert_severity] : 1;
        $min_level = isset($severity_levels[$min_severity]) ? $severity_levels[$min_severity] : 1;
        
        return $alert_level >= $min_level;
    }
    
    private function block_ip_locally($ip, $alert_data, $rule) {
        $cache_key = 'vulnity_blocked_' . md5($ip);
        if (get_transient($cache_key)) {
            return;
        }
        
        $blocked_ips = get_option($this->blocked_ips_key, array());
        
        $duration = isset($rule['duration']) ? $rule['duration'] : '24h';
        $now = time();
        $blocked_until_timestamp = $now + $this->resolve_rule_duration_seconds($rule, $duration);

        $blocked_ips[$ip] = array(
            'reason' => $alert_data['type'],
            'blocked_at' => current_time('mysql'),
            'blocked_until' => wp_date('Y-m-d H:i:s', $blocked_until_timestamp),
            'severity' => $alert_data['severity'],
            'alert_id' => $alert_data['id'],
            'rule_triggered' => isset($rule['alertType']) ? $rule['alertType'] : $rule['alert_type'],
            'source' => 'auto_block',
            'permanent' => false
        );
        
        update_option($this->blocked_ips_key, $blocked_ips);

        set_transient($cache_key, true, 60);
        $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
        $ttl = max(1, $blocked_until_timestamp - $now);
        $ttl = min(300, $ttl);
        set_transient($transient_key, 'blocked', $ttl);
        self::remember_block_transient($transient_key);

        self::$blocked_ips_cache = $blocked_ips;

        $this->firewall_manager->sync_from_options();

        $this->queue_block_notice($ip, $alert_data, $rule);
    }

    public function handle_alert_sent($alert_id, $alert) {
        if (empty($alert_id)) {
            return;
        }

        $pending = get_option($this->pending_block_notices_key, array());
        if (!isset($pending[$alert_id])) {
            return;
        }

        $entry = $pending[$alert_id];
        unset($pending[$alert_id]);
        update_option($this->pending_block_notices_key, $pending);

        if (empty($entry['ip']) || empty($entry['alert']) || empty($entry['rule'])) {
            return;
        }

        $block_delivery = $this->notify_siem_of_block($entry['ip'], $entry['alert'], $entry['rule']);
        $this->create_block_alert($entry['ip'], $entry['alert'], $entry['rule'], $block_delivery);
    }

    private function queue_block_notice($ip, $alert_data, $rule) {
        if (empty($ip) || empty($alert_data['id'])) {
            return;
        }

        $pending = get_option($this->pending_block_notices_key, array());

        $pending[$alert_data['id']] = array(
            'ip' => $ip,
            'alert' => array(
                'id' => $alert_data['id'],
                'type' => isset($alert_data['type']) ? $alert_data['type'] : 'unknown',
                'severity' => isset($alert_data['severity']) ? $alert_data['severity'] : 'low',
                'title' => isset($alert_data['title']) ? $alert_data['title'] : '',
                'message' => isset($alert_data['message']) ? $alert_data['message'] : '',
                'reason' => isset($alert_data['reason']) ? $alert_data['reason'] : '',
            ),
            'rule' => $rule,
            'queued_at' => current_time('mysql'),
        );

        update_option($this->pending_block_notices_key, $pending);
    }
    
    private function notify_siem_of_block($ip, $alert_data, $rule) {
        $config = get_option('vulnity_config');

        $duration_value = isset($rule['duration']) ? $rule['duration'] : '24h';
        $duration_label = $this->format_rule_duration_label($rule, $duration_value);
        $blocked_by = (isset($alert_data['type']) && $alert_data['type'] === 'manual_block') ? 'manual' : 'automatic';
        $blocked_by_user = isset($alert_data['blocked_by_user']) ? $alert_data['blocked_by_user'] : null;
        $reason_text = '';

        if (!empty($alert_data['reason'])) {
            $reason_text = $alert_data['reason'];
        } elseif (!empty($alert_data['message'])) {
            $reason_text = $alert_data['message'];
        } elseif (!empty($alert_data['title'])) {
            $reason_text = $alert_data['title'];
        } elseif (!empty($alert_data['type'])) {
            $reason_text = $alert_data['type'];
        } else {
            $reason_text = 'Security block';
        }

        if ($duration_value === 'permanent') {
            $message = sprintf('IP %s bloqueada de forma permanente', $ip);
        } else {
            $message = sprintf('IP %s bloqueada por %s', $ip, $duration_label);
        }

        $generic_details = array(
            'action' => 'ip_blocked',
            'ip_address' => $ip,
            'duration' => $duration_value,
            'duration_readable' => $duration_value === 'permanent' ? 'permanente' : $duration_label,
            'reason' => $reason_text,
            'blocked_by' => $blocked_by
        );

        if (!empty($alert_data['id'])) {
            $generic_details['alert_id'] = $alert_data['id'];
        }

        if (!empty($blocked_by_user)) {
            $generic_details['blocked_by_user'] = $blocked_by_user;
        }

        if (empty($config['site_id'])) {
            return array(
                'success' => false,
                'channel' => 'mitigation_update',
                'error' => 'missing_site_id'
            );
        }

        $alert_type = isset($rule['alertType']) ? $rule['alertType'] : (isset($rule['alert_type']) ? $rule['alert_type'] : 'unknown');

        $blocked_ips_stored = get_option( $this->blocked_ips_key, array() );
        $expires_at = ( $duration_value !== 'permanent' && isset( $blocked_ips_stored[ $ip ]['blocked_until'] ) )
            ? $blocked_ips_stored[ $ip ]['blocked_until']
            : null;

        $payload = array(
            'site_id'        => $config['site_id'],
            'action'         => 'ip_blocked',
            'ip_address'     => $ip,
            'reason'         => $reason_text,
            'severity'       => 'info',
            'rule_triggered' => array(
                'alert_type' => $alert_type,
                'threshold'  => isset($rule['threshold']) ? $rule['threshold'] : 0,
                'duration'   => isset($rule['duration'])  ? $rule['duration']  : '1h',
            ),
            'alert_details'  => array(
                'alert_id'      => isset($alert_data['id'])      ? $alert_data['id']      : '',
                'alert_title'   => isset($alert_data['title'])   ? $alert_data['title']   : '',
                'alert_message' => isset($alert_data['message']) ? $alert_data['message'] : '',
            ),
            'blocked_at'     => current_time('c'),
            'block_duration' => $duration_value,
            'expires_at'     => $expires_at,
            'block_type'     => $blocked_by,
            'source'         => $blocked_by === 'manual' ? 'plugin_manual_block' : 'plugin_auto_block',
        );

        $body_json = wp_json_encode($payload);

        // Prefer HMAC auth (signing_secret); fall back to token auth if not configured
        if (!empty($config['signing_secret'])) {
            $signature = base64_encode(hash_hmac('sha256', $body_json, $config['signing_secret'], true));
            $headers   = array(
                'Content-Type' => 'application/json',
                'x-signature'  => $signature,
                'x-site-id'    => $config['site_id'],
            );
        } elseif (!empty($config['token'])) {
            $headers = array(
                'Content-Type'    => 'application/json',
                'x-vulnity-token' => $config['token'],
                'x-site-id'       => $config['site_id'],
            );
        } else {
            return array(
                'success' => false,
                'channel' => 'mitigation_update',
                'error' => 'missing_auth'
            );
        }

        $args = array(
            'method'    => 'POST',
            'headers'   => $headers,
            'body'      => $body_json,
            'timeout'   => 10,
            'sslverify' => true,
        );

        $response = wp_remote_post(VULNITY_BASE_URL . '/mitigation-update', $args);

        if (is_wp_error($response)) {
            vulnity_log('[Vulnity] Block mitigation update failed: ' . $response->get_error_message());
            return array(
                'success' => false,
                'channel' => 'mitigation_update',
                'error' => $response->get_error_message()
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $response_data = json_decode($body, true);

        if ($status_code >= 200 && $status_code < 300) {
            return array(
                'success' => true,
                'channel' => 'mitigation_update',
                'data' => is_array($response_data) ? $response_data : array(
                    'message' => 'Mitigation update sent successfully'
                )
            );
        }

        vulnity_log('[Vulnity] Block mitigation update unexpected status: ' . $status_code . ' - ' . $body);

        return array(
            'success' => false,
            'channel' => 'mitigation_update',
            'error' => 'HTTP ' . $status_code
        );
    }
    
    private function create_block_alert($ip, $original_alert, $rule, $delivery_result = null) {
        $alert = array(
            'id' => 'block_' . time() . '_' . wp_generate_password(8, false),
            'type' => 'mitigation_action',
            'severity' => 'info',
            'title' => 'IP Address Auto-Blocked',
            'message' => sprintf(
                'IP %s has been automatically blocked due to %s activity (threshold: %d incidents in %s)',
                $ip,
                $original_alert['type'],
                isset($rule['threshold']) ? $rule['threshold'] : 0,
                isset($rule['duration']) ? $rule['duration'] : '1h'
            ),
            'details' => array(
                'blocked_ip' => $ip,
                'trigger_alert_type' => $original_alert['type'],
                'trigger_alert_id' => $original_alert['id'],
                'rule' => $rule,
                'block_duration' => isset($rule['duration']) ? $rule['duration'] : '24h',
                'action' => 'auto_blocked'
            ),
            'timestamp' => current_time('mysql'),
            'status' => 'new',
            'sent_to_siem' => false
        );
        
        $alerts = get_option('vulnity_alerts', array());
        array_unshift($alerts, $alert);
        $alerts = array_slice($alerts, 0, 100);
        update_option('vulnity_alerts', $alerts);

        if (is_array($delivery_result) && !empty($delivery_result['success'])) {
            $sent_alert = $this->mark_alert_sent_local($alert['id'], array(
                'success' => true,
                'data' => array(
                    'channel' => isset($delivery_result['channel']) ? $delivery_result['channel'] : 'mitigation_update',
                    'message' => 'Alert delivered via mitigation update endpoint',
                    'original_response' => isset($delivery_result['data']) && is_array($delivery_result['data']) ? $delivery_result['data'] : array()
                )
            ));
            if ($sent_alert !== null) {
                $this->dispatch_alert_sent($sent_alert['id'], $sent_alert);
            }
            return;
        }

        $siem = Vulnity_SIEM_Connector::get_instance();
        $result = $siem->send_alert($alert);
        if (!empty($result['success'])) {
            $sent_alert = $this->mark_alert_sent_local($alert['id'], $result);
            if ($sent_alert !== null) {
                $this->dispatch_alert_sent($sent_alert['id'], $sent_alert);
            }
        }
    }

    private function mark_alert_sent_local($alert_id, $result) {
        $alerts = get_option('vulnity_alerts', array());
        $sent_alert = null;

        if ($this->ensure_alert_base_available() && method_exists('Vulnity_Alert_Base', 'apply_sent_result_to_alerts')) {
            $sent_alert = Vulnity_Alert_Base::apply_sent_result_to_alerts($alerts, $alert_id, $result);
        } else {
            foreach ($alerts as &$stored_alert) {
                if ($stored_alert['id'] === $alert_id) {
                    $stored_alert['sent_to_siem'] = true;
                    $stored_alert['siem_response'] = isset($result['data']) && is_array($result['data']) ? $result['data'] : array();
                    $stored_alert['sent_at'] = current_time('mysql');
                    unset($stored_alert['last_error']);
                    $sent_alert = $stored_alert;
                    break;
                }
            }
        }

        update_option('vulnity_alerts', $alerts);
        return $sent_alert;
    }

    private function dispatch_alert_sent($alert_id, $alert) {
        if (empty($alert_id) || !is_array($alert)) {
            return;
        }

        if ($this->ensure_alert_base_available() && method_exists('Vulnity_Alert_Base', 'dispatch_alert_sent_static')) {
            Vulnity_Alert_Base::dispatch_alert_sent_static($alert_id, $alert);
            return;
        }

        do_action('vulnity_alert_sent', $alert_id, $alert);
    }

    private function ensure_alert_base_available() {
        if (class_exists('Vulnity_Alert_Base')) {
            return true;
        }

        if (!function_exists('vulnity_plugin_path')) {
            return false;
        }

        $alerts_file = vulnity_plugin_path('includes/alerts/class-alert-base.php');
        if (file_exists($alerts_file)) {
            require_once $alerts_file;
        }

        return class_exists('Vulnity_Alert_Base');
    }
    
    private function notify_siem_of_unblock($ip, $reason, $unblocked_by = null) {
        $config = get_option('vulnity_config');

        $unblocked_actor = $unblocked_by ?: 'system';
        $message = sprintf('IP %s desbloqueada', $ip);

        if (empty($config['site_id'])) {
            return;
        }

        $payload = array(
            'site_id'      => $config['site_id'],
            'action'       => 'ip_unblocked',
            'ip_address'   => $ip,
            'reason'       => $reason,
            'unblocked_at' => current_time('c'),
            'unblocked_by' => $unblocked_actor,
        );

        $body_json = wp_json_encode($payload);

        // Prefer HMAC auth (signing_secret); fall back to token auth if not configured
        if (!empty($config['signing_secret'])) {
            $signature = base64_encode(hash_hmac('sha256', $body_json, $config['signing_secret'], true));
            $headers   = array(
                'Content-Type' => 'application/json',
                'x-signature'  => $signature,
                'x-site-id'    => $config['site_id'],
            );
        } elseif (!empty($config['token'])) {
            $headers = array(
                'Content-Type'    => 'application/json',
                'x-vulnity-token' => $config['token'],
                'x-site-id'       => $config['site_id'],
            );
        } else {
            return; // No auth method available
        }

        $args = array(
            'method'    => 'POST',
            'headers'   => $headers,
            'body'      => $body_json,
            'timeout'   => 10,
            'sslverify' => true,
        );

        $response = wp_remote_post(VULNITY_BASE_URL . '/mitigation-update', $args);

        if (!is_wp_error($response)) {
            $status_code = wp_remote_retrieve_response_code($response);
            if ($status_code >= 200 && $status_code < 300) {
                return;
            }

            vulnity_log('[Vulnity] Unblock mitigation update unexpected status: ' . $status_code . ' - ' . wp_remote_retrieve_body($response));
        } else {
            vulnity_log('[Vulnity] Unblock mitigation update failed: ' . $response->get_error_message());
        }

        $this->send_generic_alert(array(
            'alert_type' => 'system_notification',
            'severity' => 'info',
            'title' => 'IP Desbloqueada',
            'message' => $message,
            'details' => array(
                'action' => 'ip_unblocked',
                'ip_address' => $ip,
                'reason' => $reason,
                'unblocked_by' => $unblocked_actor
            ),
            'system_event' => true
        ), $config);
    }

    public function dispatch_generic_alert($payload, $config = null) {
        $this->send_generic_alert($payload, $config);
    }

    private function send_generic_alert($payload, $config = null) {
        if ($config === null) {
            $config = get_option('vulnity_config');
        }

        if (empty($config['site_id']) || empty($config['token'])) {
            return;
        }

        $payload['site_id'] = $config['site_id'];
        if (!isset($payload['system_event'])) {
            $payload['system_event'] = true;
        }

        $args = array(
            'method' => 'POST',
            'headers' => array(
                'Content-Type' => 'application/json',
                'x-vulnity-token' => $config['token'],
                'x-site-id' => $config['site_id']
            ),
            'body' => wp_json_encode($payload),
            'timeout' => 15,
            'sslverify' => true
        );

        $response = wp_remote_post($this->generic_alert_endpoint, $args);

        if (is_wp_error($response)) {
            vulnity_log('[Vulnity] Failed to send generic SIEM alert: ' . $response->get_error_message()); 
            return;
        }

        $status_code = wp_remote_retrieve_response_code($response);

        if ($status_code < 200 || $status_code >= 300) {
            $body = wp_remote_retrieve_body($response);
            vulnity_log('[Vulnity] Generic SIEM alert unexpected status: ' . $status_code . ' - ' . $body); 
        }
    }
    
    private function get_client_ip() {
        return self::resolve_client_ip();
    }

    public function ajax_unblock_ip() {
        if (!$this->verify_admin_ajax_request()) {
            return;
        }

        // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is verified earlier in this handler (check_ajax_referer/verify_admin_ajax_request).
        if (!isset($_POST['ip'])) {
            wp_send_json_error('IP address required');
        }

        $ip = sanitize_text_field(wp_unslash($_POST['ip']));
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        if (empty($ip)) {
            wp_send_json_error('IP address required');
        }

        $blocked_ips = get_option($this->blocked_ips_key, array());

        if (isset($blocked_ips[$ip])) {
            unset($blocked_ips[$ip]);
            update_option($this->blocked_ips_key, $blocked_ips);

            $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
            delete_transient($transient_key);
            self::forget_block_transient($transient_key);

            self::$blocked_ips_cache = $blocked_ips;

            $this->firewall_manager->sync_from_options();

            $current_user = wp_get_current_user();
            $username = ($current_user && $current_user->exists()) ? $current_user->user_login : 'system';

            $this->notify_siem_of_unblock($ip, 'manual_unblock', $username);
            $this->push_local_mitigation_config();

            wp_send_json_success('IP unblocked successfully');
        } else {
            wp_send_json_error('IP not found in block list');
        }
    }

    public function ajax_bulk_unblock_ips() {
        if ( ! $this->verify_admin_ajax_request() ) {
            return;
        }

        $raw_ips = filter_input(INPUT_POST, 'ips', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY);

        if (null === $raw_ips) {
            $raw_ips = filter_input(INPUT_POST, 'ips', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        }

        if (null !== $raw_ips && function_exists('wp_unslash')) {
            $raw_ips = wp_unslash($raw_ips);
        } elseif (null !== $raw_ips && function_exists('stripslashes_deep')) {
            $raw_ips = stripslashes_deep($raw_ips);
        }

        $ips_input = self::get_sanitized_ips_input_from_post($raw_ips);

        $ips = array();
        foreach ((array) $ips_input as $ip) {
            $ip = sanitize_text_field($ip);
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                $ips[] = $ip;
            }
        }

        $ips = array_values(array_unique($ips));

        if (empty($ips)) {
            wp_send_json_error('No valid IP addresses provided');
        }

        $blocked_ips = get_option($this->blocked_ips_key, array());
        $unblocked = array();

        foreach ($ips as $ip) {
            if (isset($blocked_ips[$ip])) {
                unset($blocked_ips[$ip]);
                $unblocked[] = $ip;
                $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
                delete_transient($transient_key);
                self::forget_block_transient($transient_key);
            }
        }

        if (empty($unblocked)) {
            wp_send_json_error('None of the selected IPs are currently blocked');
        }

        update_option($this->blocked_ips_key, $blocked_ips);
        self::$blocked_ips_cache = $blocked_ips;

        $this->firewall_manager->sync_from_options();

        $current_user = wp_get_current_user();
        $username = ($current_user && $current_user->exists()) ? $current_user->user_login : 'system';

        foreach ($unblocked as $ip) {
            $this->notify_siem_of_unblock($ip, 'manual_bulk_unblock', $username);
        }
        $this->push_local_mitigation_config();

        $count = count($unblocked);
        $message = $count === 1
            ? '1 IP address unblocked successfully.'
            : sprintf('%d IP addresses unblocked successfully.', $count);

        wp_send_json_success(array(
            'message' => $message,
            'unblocked' => $unblocked
        ));
    }
    
    public function ajax_block_ip() {
        if ( ! $this->verify_admin_ajax_request() ) {
            return;
        }

        // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is verified earlier in this handler (check_ajax_referer/verify_admin_ajax_request).
        if (!isset($_POST['ip'])) {
            wp_send_json_error('Valid IP address required');
        }

        $ip = sanitize_text_field(wp_unslash($_POST['ip']));
        $reason = isset($_POST['reason']) ? sanitize_text_field(wp_unslash($_POST['reason'])) : 'manual_block';
        $duration = isset($_POST['duration']) ? sanitize_text_field(wp_unslash($_POST['duration'])) : 'permanent';
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            wp_send_json_error('Valid IP address required');
        }
        
        // Never allow blocking localhost
        if (in_array($ip, array('127.0.0.1', '::1'))) {
            wp_send_json_error('Cannot block localhost IP addresses');
        }
        
        $blocked_ips = get_option($this->blocked_ips_key, array());

        $current_user = wp_get_current_user();
        $blocked_by_user = ($current_user && $current_user->exists()) ? $current_user->user_login : 'system';

        $now = time();
        $blocked_until_timestamp = 'permanent' === $duration ? null : $now + $this->parse_duration($duration);

        $blocked_ips[$ip] = array(
            'reason' => $reason,
            'blocked_at' => current_time('mysql'),
            'blocked_until' => $duration === 'permanent' ? null : wp_date('Y-m-d H:i:s', $blocked_until_timestamp),
            'severity' => 'high',
            'source' => 'manual_block',
            'permanent' => $duration === 'permanent',
            'blocked_by' => $blocked_by_user
        );
        
        update_option($this->blocked_ips_key, $blocked_ips);

        $transient_key = 'vulnity_blocks_' . substr(md5($ip), 0, 8);
        $ttl = 300;
        if ($blocked_until_timestamp) {
            $ttl = max(1, $blocked_until_timestamp - $now);
            $ttl = min(300, $ttl);
        }
        set_transient($transient_key, 'blocked', $ttl);
        self::remember_block_transient($transient_key);

        self::$blocked_ips_cache = $blocked_ips;

        $this->firewall_manager->sync_from_options();

        $this->notify_siem_of_block($ip, array(
            'type' => 'manual_block',
            'severity' => 'high',
            'title' => 'Manual IP Block',
            'message' => 'IP manually blocked by administrator',
            'id' => 'manual_' . time(),
            'reason' => $reason,
            'blocked_by_user' => $blocked_by_user
        ), array(
            'alert_type' => 'manual',
            'duration' => $duration,
            'threshold' => 0
        ));
        $this->push_local_mitigation_config();

        wp_send_json_success('IP blocked successfully');
    }
    
    public function ajax_sync_mitigation() {
        if ( ! $this->verify_admin_ajax_request() ) {
            return;
        }

        $result = $this->sync_mitigation_config();

        if ($result) {
            wp_send_json_success('Mitigation configuration synced successfully');
        } else {
            wp_send_json_error('Failed to sync mitigation configuration');
        }
    }
    
    public function ajax_save_suppress_blocked_alerts() {
        if ( ! $this->verify_admin_ajax_request() ) {
            return;
        }
        // Use integers (1/0) so update_option never skips the save due to
        // false === false equality when the option hasn't been created yet.
        $enabled = (isset($_POST['enabled']) && $_POST['enabled'] === '1') ? 1 : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified via check_ajax_referer() in verify_admin_ajax_request().
        update_option('vulnity_suppress_blocked_alerts', $enabled);
        wp_send_json_success(array('enabled' => (bool) $enabled));
    }

    public function get_statistics() {
        $blocked_ips = get_option($this->blocked_ips_key, array());
        $whitelist = get_option('vulnity_ip_whitelist', array());
        $rules = get_option('vulnity_mitigation_rules', array());
        $last_sync = get_option('vulnity_last_mitigation_sync', array());
        
        $blocks_by_source = array(
            'auto_block' => 0,
            'manual_block' => 0,
            'siem_blacklist' => 0,
            'vulnity_blacklist' => 0,
            'siem_sync' => 0
        );
        
        foreach ($blocked_ips as $ip => $data) {
            $source = isset($data['source']) ? $data['source'] : 'unknown';
            if (isset($blocks_by_source[$source])) {
                $blocks_by_source[$source]++;
            } else {
                $blocks_by_source['siem_blacklist']++;
            }
        }
        
        $active_rules = 0;
        foreach ($rules as $rule) {
            if ($rule['enabled']) {
                $active_rules++;
            }
        }
        
        return array(
            'total_blocked' => count($blocked_ips),
            'whitelisted' => count($whitelist),
            'active_rules' => $active_rules,
            'blocks_by_source' => $blocks_by_source,
            'last_sync' => $last_sync,
            'php_blocking_active' => true
        );
    }
    
    public function get_blocked_ips() {
        $this->prune_expired_blocked_ips();
        return get_option($this->blocked_ips_key, array());
    }
    
    public function get_whitelist() {
        $whitelist = get_option('vulnity_ip_whitelist', array());
        
        // Always ensure localhost is in whitelist
        $localhost_ips = array('127.0.0.1', '::1');
        $updated = false;
        foreach ($localhost_ips as $localhost_ip) {
            if (!in_array($localhost_ip, $whitelist)) {
                $whitelist[] = $localhost_ip;
                $updated = true;
            }
        }
        
        if ($updated) {
            update_option('vulnity_ip_whitelist', $whitelist);
            $this->firewall_manager->sync_from_options();
        }

        return $whitelist;
    }
    
    public function get_mitigation_rules() {
        return get_option('vulnity_mitigation_rules', array());
    }

    private static function get_block_page_allowed_tags() {
        return array(
            'html'  => array(),
            'head'  => array(),
            'meta'  => array(
                'charset' => true,
                'name'    => true,
                'content' => true,
            ),
            'title' => array(),
            'link'  => array(
                'rel'  => true,
                'href' => true,
            ),
            'body'  => array(
                'class' => true,
            ),
            'div'   => array(
                'class' => true,
            ),
            'img'   => array(
                'src'   => true,
                'alt'   => true,
            ),
            'h1'    => array(),
            'strong'=> array(),
            'br'    => array(),
        );
    }

    private static function get_block_page_styles_markup($style_url) {
        $handle        = 'vulnity-block-page';
        $markup        = '';
        $safe_url      = esc_url($style_url);

        if (function_exists('wp_register_style') && function_exists('wp_enqueue_style')) {
            $version = defined('VULNITY_VERSION') ? VULNITY_VERSION : null;
            $handles = array();

            if (!function_exists('wp_style_is') || !wp_style_is($handle, 'registered')) {
                wp_register_style($handle, $safe_url, array(), $version);
            }

            wp_enqueue_style($handle);
            $handles[] = $handle;

            if (function_exists('wp_print_styles')) {
                ob_start();
                wp_print_styles($handles);
                $markup = (string) ob_get_clean();
            }
        }

        return $markup;
    }

    private static function resolve_client_ip() {
        return function_exists('vulnity_resolve_client_ip')
            ? vulnity_resolve_client_ip()
            : 'unknown';
    }

    private static function get_sanitized_ips_input_from_post($raw_ips) {
        if (null === $raw_ips) {
            return array();
        }

        if (is_string($raw_ips)) {
            $decoded = json_decode($raw_ips, true);

            if (is_array($decoded)) {
                return self::sanitize_ip_array($decoded);
            }

            $sanitized_string = self::sanitize_textarea_value($raw_ips);
            $string_ips = preg_split('/[\s,]+/', $sanitized_string, -1, PREG_SPLIT_NO_EMPTY);

            return self::sanitize_ip_array($string_ips);
        }

        if (is_array($raw_ips)) {
            return self::sanitize_ip_array($raw_ips);
        }

        return array();
    }

    private static function sanitize_ip_array($ips) {
        $sanitized = array();

        foreach ((array) $ips as $ip_value) {
            $clean_value = self::sanitize_ip_value($ip_value);

            if ($clean_value !== '') {
                $sanitized[] = $clean_value;
            }
        }

        return $sanitized;
    }

    private static function sanitize_ip_value($ip_value) {
        if (is_array($ip_value)) {
            $ip_value = reset($ip_value);
        }

        if (function_exists('wp_unslash')) {
            $ip_value = wp_unslash($ip_value);
        } elseif (function_exists('stripslashes_deep')) {
            $ip_value = stripslashes_deep($ip_value);
        }

        $ip_value = self::sanitize_text_value($ip_value);

        return is_string($ip_value) ? trim($ip_value) : '';
    }

    private static function sanitize_text_value($value) {
        if (function_exists('sanitize_text_field')) {
            return sanitize_text_field($value);
        }

        if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
            $value = (string) $value;
        } else {
            return '';
        }

        return preg_replace('/[^A-Za-z0-9:\.\-_, ]/', '', $value);
    }

    private static function sanitize_textarea_value($value) {
        if (function_exists('sanitize_textarea_field')) {
            return sanitize_textarea_field($value);
        }

        if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
            $value = (string) $value;
        } else {
            return '';
        }

        $value = preg_replace('/[^A-Za-z0-9:\.\-_,\s]/', '', $value);

        return trim($value);
    }

    private static function remember_block_transient($transient_key) {
        if (!is_string($transient_key) || $transient_key === '') {
            return;
        }

        $sanitized_key = sanitize_text_field($transient_key);
        if ($sanitized_key === '') {
            return;
        }

        $index = self::get_block_transient_index();

        if (!in_array($sanitized_key, $index, true)) {
            $index[] = $sanitized_key;
            self::update_block_transient_index($index);
        }
    }

    private static function forget_block_transient($transient_key) {
        if (!is_string($transient_key) || $transient_key === '') {
            return;
        }

        $sanitized_key = sanitize_text_field($transient_key);
        if ($sanitized_key === '') {
            return;
        }

        $index = self::get_block_transient_index();
        $position = array_search($sanitized_key, $index, true);

        if ($position !== false) {
            unset($index[$position]);
            self::update_block_transient_index($index);
        }
    }

    private static function get_block_transient_index() {
        $stored = get_option(self::BLOCK_TRANSIENT_INDEX, array());

        if (!is_array($stored)) {
            return array();
        }

        $normalized = array();
        foreach ($stored as $transient_key) {
            if (is_string($transient_key) && $transient_key !== '') {
                $normalized[] = sanitize_text_field($transient_key);
            }
        }

        $normalized = array_values(array_unique($normalized));
        $active_transients = array();

        foreach ($normalized as $transient_key) {
            if (get_transient($transient_key) !== false) {
                $active_transients[] = $transient_key;
            }
        }

        if (count($active_transients) !== count($normalized)) {
            self::update_block_transient_index($active_transients);
        }

        return $active_transients;
    }

    private static function update_block_transient_index($index) {
        $clean_index = array();

        foreach ((array) $index as $transient_key) {
            if (is_string($transient_key) && $transient_key !== '') {
                $clean_index[] = sanitize_text_field($transient_key);
            }
        }

        $clean_index = array_values(array_unique($clean_index));

        if (count($clean_index) > self::BLOCK_TRANSIENT_INDEX_LIMIT) {
            $clean_index = array_slice($clean_index, -self::BLOCK_TRANSIENT_INDEX_LIMIT);
        }

        if (empty($clean_index)) {
            delete_option(self::BLOCK_TRANSIENT_INDEX);
            return;
        }

        update_option(self::BLOCK_TRANSIENT_INDEX, $clean_index, false);
    }

    public function get_last_sync_info() {
        $last_sync = get_option('vulnity_last_config_sync', false);
        $sync_details = get_option('vulnity_last_mitigation_sync', array());
        
        if (!$last_sync && empty($sync_details)) {
            return array(
                'status' => 'never',
                'message' => 'Never synchronized',
                'timestamp' => null
            );
        }
        
        return array(
            'status' => isset($sync_details['status']) ? $sync_details['status'] : 'unknown',
            'message' => isset($sync_details['status']) && $sync_details['status'] === 'success' ? 'Configuration synced successfully' : 'Sync failed',
            'timestamp' => $last_sync ? $last_sync : (isset($sync_details['timestamp']) ? $sync_details['timestamp'] : null),
            'last_updated' => isset($sync_details['last_updated']) ? $sync_details['last_updated'] : null
        );
    }
    
    public function cleanup() {
        wp_clear_scheduled_hook('vulnity_sync_mitigation_config');
        
        delete_option($this->blocked_ips_key);
        delete_option('vulnity_ip_whitelist');
        delete_option('vulnity_mitigation_rules');
        delete_option('vulnity_last_mitigation_sync');
        delete_option('vulnity_last_config_sync');

        $this->clear_all_block_cache();

        $this->firewall_manager->remove_firewall();
    }
}
