<?php
/**
 * Google reCAPTCHA Integration for BotFend Anti-Bot Firewall
 *
 * @package BotFend\ThirdParty
 * @since 4.4.0
 * @version 4.8.7 - Complete logic refactor: Load always, validate only when keys verified
 */

namespace BotFend\ThirdParty;

use BotFend\Core\Config;
use WP_Error;

defined('ABSPATH') || exit;

class Recaptcha {

    private Config $config;
    private string $version = 'v3';
    private string $site_key = '';
    private string $secret_key = '';
    private float $threshold = 0.5;
    private bool $enabled = false;
    private bool $bypass_whitelist = false;
    private array $form_locations = [];

    private ?bool $keys_api_valid = null;

    private const VALID_ACTIONS = [
        'login'        => 'login',
        'registration' => 'registration',
        'lostpassword' => 'lostpassword',
        'comment'      => 'comment',
        'submit'       => 'submit',
    ];

    public function __construct() {
        $this->config = Config::get_instance();
        $this->load_settings();

        // NEW LOGIC: Hook everything if enabled and configured structurally
        // We'll check API validity at verification time, not render time
        if ($this->enabled && $this->is_configured()) {
            $this->init_hooks();
        }
    }

    private function load_settings(): void {
        $this->enabled          = $this->config->get_bool('enable_recaptcha', false);
        $this->version          = $this->config->get_string('recaptcha_version', 'v3');
        $this->threshold        = (float) $this->config->get_float('recaptcha_v3_threshold', 0.5);
        $this->bypass_whitelist = $this->config->get_bool('recaptcha_bypass_whitelist', false);

        $this->form_locations = [
            'login'            => $this->config->get_bool('recaptcha_on_login', false),
            'registration'     => $this->config->get_bool('recaptcha_on_registration', false),
            'lostpassword'     => $this->config->get_bool('recaptcha_on_lostpassword', false),
            'comment'          => $this->config->get_bool('enable_comment_recaptcha', false),
            'comment_loggedin' => $this->config->get_bool('comment_recaptcha_loggedin', false),
        ];

        if ($this->version === 'v3') {
            $this->site_key   = $this->config->get_string('recaptcha_v3_site_key', '');
            $this->secret_key = $this->config->get_string('recaptcha_v3_secret_key', '');
        } else {
            $this->site_key   = $this->config->get_string('recaptcha_v2_site_key', '');
            $this->secret_key = $this->config->get_string('recaptcha_v2_secret_key', '');
        }
    }

    // ──────────────────────────────────────────────
    // Public State
    // ──────────────────────────────────────────────

    public function is_configured(): bool {
        $site_key   = trim($this->site_key);
        $secret_key = trim($this->secret_key);

        if (empty($site_key) || empty($secret_key)) {
            return false;
        }

        $key_pattern = '/^[a-zA-Z0-9_\-]{40}$/';
        return (bool) preg_match($key_pattern, $site_key) && (bool) preg_match($key_pattern, $secret_key);
    }

    /**
     * NEW: Should we render the reCAPTCHA widget?
     * Returns true if enabled AND structurally configured (valid key format).
     * Does NOT require API validation - we load the widget optimistically.
     */
    public function should_render(): bool {
        return $this->enabled && $this->is_configured();
    }

    /**
     * NEW: Should we verify the token?
     * Returns true only if keys are confirmed valid via Google API.
     * This prevents failed verifications due to invalid keys.
     */
    public function should_verify(): bool {
        return $this->enabled && $this->is_configured() && $this->are_keys_api_valid();
    }

    /**
     * Check if keys have been verified against Google API.
     */
    public function are_keys_api_valid(): bool {
        if ($this->keys_api_valid === null) {
            // Attempt validation on demand
            $this->validate_keys();
        }
        return $this->keys_api_valid ?? false;
    }

    // ──────────────────────────────────────────────
    // Key Validation (lazy, on-demand)
    // ──────────────────────────────────────────────

    /**
     * Validate keys when needed. Caches result to avoid hitting Google on every request.
     */
    private function validate_keys(): void {
        $cache_key = 'botfend_recaptcha_api_valid_' . md5($this->site_key . $this->secret_key);
        $cached    = get_transient($cache_key);

        if ($cached === 'valid') {
            $this->keys_api_valid = true;
            return;
        }
        if ($cached === 'invalid') {
            $this->keys_api_valid = false;
            return;
        }

        // Only validate once per request
        $valid = $this->perform_api_key_validation();
        $this->keys_api_valid = $valid;
        set_transient($cache_key, $valid ? 'valid' : 'invalid', $valid ? DAY_IN_SECONDS : HOUR_IN_SECONDS);
    }

    private function perform_api_key_validation(): bool {
        $result = $this->call_google_api($this->secret_key, 'key_validation_' . time());

        if (is_wp_error($result)) {
            // Network failure - don't cache, try again next time
            $this->keys_api_valid = null;
            return false;
        }

        $error_codes = $result['error-codes'] ?? [];

        // Valid keys return ONLY "invalid-input-response" (because we sent a fake token)
        if (in_array('invalid-input-response', $error_codes) &&
            !in_array('invalid-input-secret', $error_codes) &&
            !in_array('missing-input-secret', $error_codes)) {
            return true;
        }

        return false;
    }

    // ──────────────────────────────────────────────
    // Hooks (always registered if configured)
    // ──────────────────────────────────────────────

    private function init_hooks(): void {
        // Render hooks - always run if configured
        if ($this->form_locations['login']) {
            add_action('login_form', [$this, 'render_login_recaptcha']);
        }
        if ($this->form_locations['registration']) {
            add_action('register_form', [$this, 'render_registration_recaptcha']);
        }
        if ($this->form_locations['lostpassword']) {
            add_action('lostpassword_form', [$this, 'render_lostpassword_recaptcha']);
        }
        if ($this->form_locations['comment']) {
            add_action('comment_form_after_fields', [$this, 'render_comment_recaptcha']);
        }

        // Verification hooks - always registered, but check should_verify() internally
        if ($this->form_locations['login']) {
            add_filter('wp_authenticate_user', [$this, 'verify_login_recaptcha'], 10, 2);
        }
        if ($this->form_locations['registration']) {
            add_filter('registration_errors', [$this, 'verify_registration_recaptcha'], 10, 3);
        }
        if ($this->form_locations['lostpassword']) {
            add_filter('lostpassword_errors', [$this, 'verify_lostpassword_recaptcha'], 10, 2);
        }
        if ($this->form_locations['comment']) {
            add_filter('preprocess_comment', [$this, 'verify_comment_recaptcha']);
        }

        // Script enqueuing - always run if configured
        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
        add_action('login_enqueue_scripts', [$this, 'enqueue_scripts']);
        
        // Admin AJAX for testing
        add_action('wp_ajax_botfend_test_recaptcha', [$this, 'ajax_test_recaptcha']);
    }

    // ──────────────────────────────────────────────
    // Scripts (simplified - no action detection needed)
    // ──────────────────────────────────────────────

    public function enqueue_scripts(): void {
        // Only load if we should render
        if (!$this->should_render()) {
            return;
        }

        // Skip for whitelisted IPs
        if ($this->is_ip_whitelisted($this->get_client_ip())) {
            return;
        }

        // NEW: Determine action based on current context WITHOUT conditional tags
        $action = $this->determine_action_safely();

        if ($this->version === 'v2') {
            wp_enqueue_script('google-recaptcha', 'https://www.google.com/recaptcha/api.js', [], null, true);
        } else {
            wp_enqueue_script(
                'google-recaptcha',
                add_query_arg('render', urlencode($this->site_key), 'https://www.google.com/recaptcha/api.js'),
                [],
                null,
                true
            );
            wp_add_inline_script('google-recaptcha', $this->get_inline_script($action), 'after');
        }
    }

    private function get_inline_script(string $action): string {
        return sprintf(
            "(function(){var b={ready:!1,token:null,action:%s,init:function(){" .
            "if(typeof grecaptcha==='undefined')return;" .
            "grecaptcha.ready(function(){b.ready=!0;b.getToken();" .
            "setInterval(function(){document.querySelector('.g-recaptcha-response')&&b.getToken()},110000)})}," .
            "getToken:function(){b.ready&&grecaptcha.execute('%s',{action:b.action})" .
            ".then(function(t){b.token=t;document.querySelectorAll('.g-recaptcha-response').forEach(function(f){f.value=t})})" .
            ".catch(function(e){console.warn('reCAPTCHA error:',e)})}};" .
            "document.readyState==='loading'?document.addEventListener('DOMContentLoaded',function(){b.init()}):b.init()})()",
            wp_json_encode($action),
            esc_js($this->site_key)
        );
    }

    // ──────────────────────────────────────────────
    // NEW: Safe Action Detection (No conditional tags)
    // ──────────────────────────────────────────────

    /**
     * Determine the appropriate reCAPTCHA action based on context.
     * 
     * COMPLETELY REFACTORED: Uses only safe detection methods that work at any
     * point in the WordPress lifecycle. No conditional tags, no query dependency.
     *
     * @return string
     */
    private function determine_action_safely(): string {
        // 1. Check for WordPress login page (safest - uses global variable)
        if (isset($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'wp-login.php') {
            if (isset($_GET['action']) && $_GET['action'] === 'lostpassword') {
                return self::VALID_ACTIONS['lostpassword'];
            }
            if (isset($_GET['action']) && $_GET['action'] === 'register') {
                return self::VALID_ACTIONS['registration'];
            }
            return self::VALID_ACTIONS['login'];
        }

        // 2. Check for POST submissions (always safe - request data)
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            if (isset($_POST['wp-submit']) && isset($_POST['reg_passmail'])) {
                return self::VALID_ACTIONS['registration'];
            }
            if (isset($_POST['comment_post_ID'])) {
                return self::VALID_ACTIONS['comment'];
            }
        }

        // 3. Check for AJAX (always safe - uses WordPress function)
        if (wp_doing_ajax()) {
            if (isset($_POST['action']) && $_POST['action'] === 'submit_comment') {
                return self::VALID_ACTIONS['comment'];
            }
            // Generic AJAX
            return self::VALID_ACTIONS['submit'];
        }

        // 4. URL-based detection (safe - uses $_SERVER)
        $request_uri = $_SERVER['REQUEST_URI'] ?? '';
        
        // Check for wp-login.php in URL (catch redirects)
        if (strpos($request_uri, 'wp-login.php') !== false) {
            if (strpos($request_uri, 'action=lostpassword') !== false) {
                return self::VALID_ACTIONS['lostpassword'];
            }
            if (strpos($request_uri, 'action=register') !== false) {
                return self::VALID_ACTIONS['registration'];
            }
            return self::VALID_ACTIONS['login'];
        }

        // Check for wp-comments-post.php
        if (strpos($request_uri, 'wp-comments-post.php') !== false) {
            return self::VALID_ACTIONS['comment'];
        }

        // 5. Check for comment form on single posts/pages
        // ONLY if WordPress is loaded enough to check safely
        if (did_action('wp') && !is_admin()) {
            global $wp_query;
            if ($wp_query instanceof \WP_Query && $wp_query->is_singular()) {
                $post_id = $wp_query->get_queried_object_id();
                if ($post_id && comments_open($post_id)) {
                    return self::VALID_ACTIONS['comment'];
                }
            }
        }

        // 6. Default - generic form submission
        return self::VALID_ACTIONS['submit'];
    }

    // ──────────────────────────────────────────────
    // Render (simplified - no API validation needed)
    // ──────────────────────────────────────────────

    public function render_recaptcha(string $action = 'submit'): void {
        // Only render if structurally configured (key format is valid)
        if (!$this->should_render()) {
            return;
        }

        // Skip for whitelisted IPs
        if ($this->is_ip_whitelisted($this->get_client_ip())) {
            echo '<input type="hidden" name="recaptcha_skipped" value="1">';
            return;
        }

        // Always render the widget - even if API keys aren't verified yet
        // The verification will handle invalid keys gracefully
        if ($this->version === 'v2') {
            printf(
                '<div class="g-recaptcha" data-sitekey="%s" style="margin:15px 0"></div>',
                esc_attr($this->site_key)
            );
            echo '<input type="hidden" name="recaptcha_version" value="v2">';
        } else {
            echo '<input type="hidden" class="g-recaptcha-response" name="g-recaptcha-response" value="">';
            echo '<input type="hidden" name="recaptcha_version" value="v3">';
            printf('<input type="hidden" name="recaptcha_action" value="%s">', esc_attr($action));
        }

        // Noscript fallback
        printf(
            '<noscript><div style="width:302px;height:422px;margin:15px 0">' .
            '<iframe src="https://www.google.com/recaptcha/api/fallback?k=%s" frameborder="0" scrolling="no" ' .
            'style="width:302px;height:422px;border-style:none"></iframe></div></noscript>',
            esc_attr($this->site_key)
        );
    }

    public function render_login_recaptcha(): void { 
        $this->render_recaptcha(self::VALID_ACTIONS['login']); 
    }
    
    public function render_registration_recaptcha(): void { 
        $this->render_recaptcha(self::VALID_ACTIONS['registration']); 
    }
    
    public function render_lostpassword_recaptcha(): void { 
        $this->render_recaptcha(self::VALID_ACTIONS['lostpassword']); 
    }
    
    public function render_comment_recaptcha(): void {
        // Respect logged-in user setting
        if (is_user_logged_in() && !$this->form_locations['comment_loggedin']) {
            return;
        }
        $this->render_recaptcha(self::VALID_ACTIONS['comment']);
    }

    // ──────────────────────────────────────────────
    // Verification (with intelligent bypass)
    // ──────────────────────────────────────────────

    /**
     * Verify reCAPTCHA token with enterprise fail-safe.
     * 
     * REFACTORED: Now checks should_verify() internally and gracefully bypasses
     * when keys aren't validated yet. This prevents lockouts during initial setup.
     */
    public function verify_token(string $token, string $action = 'submit') {
        // Bypass for whitelisted IPs
        if (isset($_POST['recaptcha_skipped']) && $_POST['recaptcha_skipped'] === '1') {
            return ['success' => true, 'skipped' => true, 'reason' => 'whitelisted'];
        }

        // If verification is disabled or keys invalid, bypass gracefully
        if (!$this->should_verify()) {
            // Log for debugging
            if (!$this->are_keys_api_valid() && $this->is_configured()) {
                error_log('BotFend reCAPTCHA: Bypassing verification - API keys not yet validated');
            }
            return ['success' => true, 'skipped' => true, 'reason' => 'keys_not_validated'];
        }

        // If no token but we have valid keys, require it
        if (empty($token)) {
            return new WP_Error('recaptcha_required', 
                esc_html__('Please complete the reCAPTCHA verification.', 'botfend-anti-bot-firewall'));
        }

        // Verify with Google
        $result = $this->call_google_api($this->secret_key, $token, $this->get_client_ip());

        // ENTERPRISE FAIL-SAFE: Network timeout/error → fail open
        if (is_wp_error($result)) {
            error_log('BotFend reCAPTCHA Network Failure: ' . $result->get_error_message() . '. Fail open.');
            return ['success' => true, 'score' => 1.0, 'action' => $action, 'skipped' => true, 'reason' => 'network_error'];
        }

        // Check for failure
        if (empty($result['success'])) {
            $error_codes = $result['error-codes'] ?? [];

            // ENTERPRISE FAIL-SAFE: Config errors → fail open, invalidate cache
            $critical_config_errors = ['missing-input-secret', 'invalid-input-secret', 'bad-request'];
            foreach ($critical_config_errors as $critical_error) {
                if (in_array($critical_error, $error_codes, true)) {
                    error_log(sprintf('BotFend reCAPTCHA Config Error [%s]. Failing open.', $critical_error));
                    $this->keys_api_valid = false;
                    set_transient(
                        'botfend_recaptcha_api_valid_' . md5($this->site_key . $this->secret_key), 
                        'invalid', 
                        HOUR_IN_SECONDS
                    );
                    return ['success' => true, 'score' => 1.0, 'action' => $action, 'skipped' => true, 'reason' => 'config_error'];
                }
            }

            // Actual user failure
            return new WP_Error('recaptcha_failed', $this->get_error_message($error_codes));
        }

        // V3-specific checks
        if ($this->version === 'v3' && isset($result['score'])) {
            if ($result['score'] < $this->threshold) {
                return new WP_Error(
                    'recaptcha_low_score',
                    sprintf(
                        esc_html__('reCAPTCHA score too low (%.2f). Minimum required: %.2f', 'botfend-anti-bot-firewall'), 
                        $result['score'], 
                        $this->threshold
                    )
                );
            }

            // Action mismatch check
            $posted_action = isset($_POST['recaptcha_action']) ? 
                sanitize_text_field(wp_unslash($_POST['recaptcha_action'])) : $action;
                
            if (isset($result['action']) && $result['action'] !== $posted_action) {
                if ($result['action'] !== 'submit' && $posted_action !== 'submit') {
                    return new WP_Error('recaptcha_action_mismatch',
                        sprintf(
                            esc_html__('reCAPTCHA action mismatch. Expected: %s, Received: %s', 'botfend-anti-bot-firewall'), 
                            $posted_action, 
                            $result['action']
                        )
                    );
                }
            }
        }

        return ['success' => true, 'score' => $result['score'] ?? 1.0, 'action' => $result['action'] ?? $action];
    }

    /**
     * Centralized Google API call.
     */
    private function call_google_api(string $secret, string $token, string $remote_ip = ''): array|WP_Error {
        $body = ['secret' => $secret, 'response' => $token];
        if (!empty($remote_ip)) {
            $body['remoteip'] = $remote_ip;
        }

        $response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
            'body'      => $body,
            'timeout'   => 10,
            'sslverify' => true,
        ]);

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

        if (wp_remote_retrieve_response_code($response) !== 200) {
            return new WP_Error('recaptcha_http_error', 
                esc_html__('reCAPTCHA API error.', 'botfend-anti-bot-firewall'));
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        if ($body === null) {
            return new WP_Error('recaptcha_invalid_json', 
                esc_html__('Invalid response.', 'botfend-anti-bot-firewall'));
        }

        return $body;
    }

    // ──────────────────────────────────────────────
    // Form Verifiers (all use should_verify() internally)
    // ──────────────────────────────────────────────

    public function verify_login_recaptcha($user, string $password) {
        // Early returns for disabled/whitelisted
        if (!$this->form_locations['login']) return $user;
        if ($this->is_ip_whitelisted($this->get_client_ip())) return $user;
        if (!$this->enabled || !$this->is_configured()) return $user;

        $token = isset($_POST['g-recaptcha-response']) ? 
            sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
            
        $verification = $this->verify_token($token, self::VALID_ACTIONS['login']);
        return is_wp_error($verification) ? $verification : $user;
    }

    public function verify_registration_recaptcha($errors, $sanitized_user_login, $user_email) {
        if (!$this->form_locations['registration']) return $errors;
        if ($this->is_ip_whitelisted($this->get_client_ip())) return $errors;
        if (!$this->enabled || !$this->is_configured()) return $errors;

        $token = isset($_POST['g-recaptcha-response']) ? 
            sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
            
        $verification = $this->verify_token($token, self::VALID_ACTIONS['registration']);
        if (is_wp_error($verification)) {
            $errors->add('recaptcha_failed', $verification->get_error_message());
        }
        return $errors;
    }

    public function verify_lostpassword_recaptcha($errors) {
        if (!$this->form_locations['lostpassword']) return $errors;
        if ($this->is_ip_whitelisted($this->get_client_ip())) return $errors;
        if (!$this->enabled || !$this->is_configured()) return $errors;

        $token = isset($_POST['g-recaptcha-response']) ? 
            sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
            
        $verification = $this->verify_token($token, self::VALID_ACTIONS['lostpassword']);
        if (is_wp_error($verification)) {
            $errors->add('recaptcha_failed', $verification->get_error_message());
        }
        return $errors;
    }

    public function verify_comment_recaptcha(array $commentdata): array {
        if (!$this->form_locations['comment']) return $commentdata;
        if ($this->is_ip_whitelisted($this->get_client_ip())) return $commentdata;
        if (is_user_logged_in() && !$this->form_locations['comment_loggedin']) return $commentdata;
        if (!$this->enabled || !$this->is_configured()) return $commentdata;

        $token = isset($_POST['g-recaptcha-response']) ? 
            sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
            
        $verification = $this->verify_token($token, self::VALID_ACTIONS['comment']);
        if (is_wp_error($verification)) {
            wp_die(
                esc_html($verification->get_error_message()), 
                esc_html__('Verification Failed', 'botfend-anti-bot-firewall'), 
                ['response' => 403]
            );
        }
        return $commentdata;
    }

    // ──────────────────────────────────────────────
    // AJAX Test (unchanged)
    // ──────────────────────────────────────────────

    public function ajax_test_recaptcha(): void {
        if (!check_ajax_referer('botfend_admin_nonce', 'nonce', false)) {
            wp_send_json_error(['message' => esc_html__('Invalid security token.', 'botfend-anti-bot-firewall')]);
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => esc_html__('Unauthorized.', 'botfend-anti-bot-firewall')]);
            return;
        }

        $version = isset($_POST['version']) ? sanitize_text_field(wp_unslash($_POST['version'])) : $this->version;
        $site_key   = isset($_POST['site_key']) ? sanitize_text_field(wp_unslash($_POST['site_key'])) : '';
        $secret_key = isset($_POST['secret_key']) ? sanitize_text_field(wp_unslash($_POST['secret_key'])) : '';

        if (empty($site_key) || empty($secret_key)) {
            if ($version === 'v3') {
                $site_key   = $this->config->get_string('recaptcha_v3_site_key', '');
                $secret_key = $this->config->get_string('recaptcha_v3_secret_key', '');
            } else {
                $site_key   = $this->config->get_string('recaptcha_v2_site_key', '');
                $secret_key = $this->config->get_string('recaptcha_v2_secret_key', '');
            }
        }

        if (empty($site_key) || empty($secret_key)) {
            wp_send_json_error([
                'message'    => esc_html__('Please enter both Site Key and Secret Key.', 'botfend-anti-bot-firewall'),
                'error_type' => 'missing_keys',
            ]);
            return;
        }

        $key_pattern = '/^[a-zA-Z0-9_\-]{40}$/';
        if (!preg_match($key_pattern, $site_key) || !preg_match($key_pattern, $secret_key)) {
            wp_send_json_error([
                'message'    => esc_html__('Invalid key format. reCAPTCHA keys must be 40 characters.', 'botfend-anti-bot-firewall'),
                'error_type' => 'invalid_format',
            ]);
            return;
        }

        $result = $this->call_google_api($secret_key, 'test_token_' . time());

        if (is_wp_error($result)) {
            wp_send_json_error([
                'message'    => esc_html__('API connection failed: ', 'botfend-anti-bot-firewall') . $result->get_error_message(),
                'error_type' => 'connection',
            ]);
            return;
        }

        $error_codes = $result['error-codes'] ?? [];
        $ver_label   = strtoupper($version);

        if (in_array('invalid-input-secret', $error_codes) || in_array('missing-input-secret', $error_codes)) {
            wp_send_json_error([
                'message'    => sprintf(
                    esc_html__('reCAPTCHA %s keys are invalid! Please check your Secret Key.', 'botfend-anti-bot-firewall'),
                    $ver_label
                ),
                'error_type' => 'invalid_secret',
                'error_codes' => $error_codes,
            ]);
            return;
        }

        if (in_array('invalid-input-response', $error_codes)) {
            wp_send_json_success([
                'message'         => sprintf(
                    esc_html__('reCAPTCHA %s keys are valid!', 'botfend-anti-bot-firewall'),
                    $ver_label
                ),
                'version'         => $version,
                'site_key_masked' => $this->mask_key($site_key),
            ]);
            return;
        }

        wp_send_json_error([
            'message'    => sprintf(
                esc_html__('reCAPTCHA %s test failed. Unexpected API response.', 'botfend-anti-bot-firewall'),
                $ver_label
            ),
            'error_type' => 'unexpected',
            'error_codes' => $error_codes,
        ]);
    }

    // ──────────────────────────────────────────────
    // Utility (unchanged)
    // ──────────────────────────────────────────────

    private function is_ip_whitelisted(string $ip): bool {
        if (!$this->bypass_whitelist) return false;
        $whitelist = $this->config->get_string('botfend_whitelist_ips', '');
        if (empty($whitelist)) return false;
        foreach (array_filter(array_map('trim', explode("\n", $whitelist))) as $line) {
            if ($line === '' || str_starts_with($line, '#')) continue;
            if (str_contains($line, '/')) { 
                if ($this->ip_in_cidr($ip, $line)) return true; 
            }
            elseif ($ip === $line) return true;
        }
        return false;
    }

    private function ip_in_cidr(string $ip, string $range): bool {
        if (!str_contains($range, '/')) return $ip === $range;
        [$subnet, $bits] = explode('/', $range);
        $bits = (int) $bits;
        $ip_long = ip2long($ip);
        $subnet_long = ip2long($subnet);
        if ($ip_long === false || $subnet_long === false) return false;
        $mask = -1 << (32 - $bits);
        return ($ip_long & $mask) === ($subnet_long & $mask);
    }

    private function get_client_ip(): string {
        foreach (['HTTP_CF_CONNECTING_IP','HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','HTTP_CLIENT_IP','REMOTE_ADDR'] as $h) {
            if (!empty($_SERVER[$h])) {
                $ip = trim(explode(',', sanitize_text_field(wp_unslash($_SERVER[$h])))[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
            }
        }
        return '0.0.0.0';
    }

    private function get_error_message(array $error_codes): string {
        $map = [
            'missing-input-secret'   => esc_html__('The secret key is missing.', 'botfend-anti-bot-firewall'),
            'invalid-input-secret'   => esc_html__('The secret key is invalid.', 'botfend-anti-bot-firewall'),
            'missing-input-response' => esc_html__('The response token is missing.', 'botfend-anti-bot-firewall'),
            'invalid-input-response' => esc_html__('The response token is invalid.', 'botfend-anti-bot-firewall'),
            'bad-request'            => esc_html__('The request to reCAPTCHA is invalid.', 'botfend-anti-bot-firewall'),
            'timeout-or-duplicate'   => esc_html__('The response token has expired or already been used.', 'botfend-anti-bot-firewall'),
        ];
        foreach ($error_codes as $code) {
            if (isset($map[$code])) return $map[$code];
        }
        return esc_html__('reCAPTCHA verification failed. Please try again.', 'botfend-anti-bot-firewall');
    }

    private function mask_key(string $key): string {
        return strlen($key) <= 8 ? '••••••••' : substr($key, 0, 4) . '••••' . substr($key, -4);
    }

    // ──────────────────────────────────────────────
    // Status / Public Test (unchanged)
    // ──────────────────────────────────────────────

    public function get_status(): array {
        return [
            'enabled'               => $this->enabled,
            'version'               => $this->version,
            'configured'            => $this->is_configured(),
            'should_render'         => $this->should_render(),
            'should_verify'         => $this->should_verify(),
            'keys_api_valid'        => $this->are_keys_api_valid(),
            'site_key_configured'   => !empty($this->site_key),
            'secret_key_configured' => !empty($this->secret_key),
            'threshold'             => $this->threshold,
            'bypass_whitelist'      => $this->bypass_whitelist,
            'form_locations'        => $this->form_locations,
        ];
    }

    public function test_connection(): array {
        if (!$this->is_configured()) {
            return ['success' => false, 'message' => esc_html__('API keys are not configured or format is invalid.', 'botfend-anti-bot-firewall')];
        }

        $result = $this->call_google_api($this->secret_key, 'test_token');
        if (is_wp_error($result)) {
            return ['success' => false, 'message' => esc_html__('Connection failed.', 'botfend-anti-bot-firewall')];
        }

        return [
            'success'         => true,
            'message'         => esc_html__('API connection successful!', 'botfend-anti-bot-firewall'),
            'version'         => $this->version,
            'site_key_masked' => $this->mask_key($this->site_key),
        ];
    }
}