<?php
/**
 * Cloudflare API Wrapper for BotFend Anti-Bot Firewall
 *
 * Handles all direct API communication with Cloudflare's API v4.
 * Production-ready with comprehensive error handling, retry support,
 * request logging, and security hardening.
 *
 * @package BotFend\Cloudflare
 * @since 4.9.0
 */

namespace BotFend\Cloudflare;

use BotFend\Core\Config;
use BotFend\Security\Encryption;

defined('ABSPATH') || exit;

/**
 * Cloudflare API class.
 *
 * Production-ready implementation with:
 * - Comprehensive error code mapping for retry logic
 * - Rate limit handling with Retry-After support
 * - Request/response logging for debugging
 * - Input validation and security hardening
 * - Configurable timeouts
 * - User-Agent header
 * - Zone ID caching
 * - 5xx error detection
 *
 * @since 4.9.0
 */
class CloudflareAPI {

    /**
     * Config instance.
     *
     * @since 4.9.0
     * @var Config
     */
    private $config;

    /**
     * Encryption instance.
     *
     * @since 4.9.0
     * @var Encryption
     */    private $encryption;

    /**
     * Cloudflare API base URL.
     *
     * @since 4.9.0
     * @var string
     */
    private $api_base = 'https://api.cloudflare.com/client/v4';

    /**
     * Cached zone ID.
     *
     * @since 4.9.0
     * @var string|null
     */
    private $cached_zone_id = null;

    /**
     * Request timeout in seconds.
     *
     * @since 4.9.0
     * @var int
     */
    private $timeout = 30;

    /**
     * Constructor.
     *
     * @since 4.9.0
     *
     * @param Config     $config     Config instance.
     * @param Encryption $encryption Encryption instance.
     */
    public function __construct(Config $config, Encryption $encryption) {
        $this->config     = $config;
        $this->encryption = $encryption;
        
        // Initialize timeout from config
        $this->timeout = $this->config->get_int('cf_api_timeout', 30);
        
        // Ensure timeout is within reasonable bounds
        $this->timeout = max(5, min(60, $this->timeout));
    }

    /**
     * Make API request to Cloudflare.
     *
     * @since 4.9.0
     *     * @param string $method   HTTP method (GET, POST, PUT, DELETE, PATCH).
     * @param string $endpoint API endpoint.
     * @param ?array $body     Request body (optional).
     * @return array|\WP_Error Response data or WP_Error on failure.
     */
    private function request(string $method, string $endpoint, ?array $body = null) {
        $url = $this->api_base . '/' . ltrim($endpoint, '/');
        
        $args = [
            'method'    => strtoupper($method),
            'timeout'   => $this->timeout,
            'headers'   => $this->get_headers(),
            'sslverify' => true,
        ];
        
        if ($body !== null) {
            $args['body'] = wp_json_encode($body);
        }
        
        // Log request for debugging
        $this->log_request($method, $endpoint, $body);
        
        $response = wp_remote_request($url, $args);
        
        // Handle WP HTTP errors
        if (is_wp_error($response)) {
            $error_code = $response->get_error_code();
            $mapped_code = $this->map_wp_error_code($error_code);
            
            $this->log_response($method, $endpoint, $mapped_code, $response->get_error_message());
            
            return new \WP_Error(
                $mapped_code,
                $response->get_error_message(),
                ['original_code' => $error_code, 'endpoint' => $endpoint]
            );
        }
        
        $status_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);
        $data = json_decode($response_body, true);
        
        // Log response
        $this->log_response($method, $endpoint, $status_code, $data);
        
        // Handle HTTP status codes
        $error = $this->handle_http_status($status_code, $data, $endpoint, $response);
        if (is_wp_error($error)) {
            return $error;
        }        
        // Check for API errors
        if (!isset($data['success']) || $data['success'] !== true) {
            return $this->handle_api_error($data, $endpoint);
        }
        
        return $data;
    }

    /**
     * Map WP HTTP error codes to retryable codes.
     *
     * @since 4.9.0
     *
     * @param string $wp_code WordPress error code.
     * @return string Mapped error code.
     */
    private function map_wp_error_code(string $wp_code): string {
        $mapping = [
            'http_request_failed' => 'http_request_failed',
            'connect_timeout'     => 'timeout',
            'timeout'             => 'timeout',
            'ssl_verify_failed'   => 'ssl_verify_failed',
        ];
        
        return $mapping[$wp_code] ?? $wp_code;
    }

    /**
     * Handle HTTP status codes and return appropriate errors.
     *
     * @since 4.9.0
     *
     * @param int         $status_code HTTP status code.
     * @param array       $data        Response data.
     * @param string      $endpoint    API endpoint.
     * @param array       $response    Full response.
     * @return \WP_Error|null Error or null if OK.
     */
    private function handle_http_status(int $status_code, array $data, string $endpoint, array $response): ?\WP_Error {
        // Success codes
        if ($status_code >= 200 && $status_code < 300) {
            return null;
        }
        
        // Rate limiting
        if ($status_code === 429) {
            $retry_after = $this->get_retry_after($response);
            
            return new \WP_Error(                'rate_limited',
                __('Cloudflare API rate limit reached.', 'botfend-anti-bot-firewall'),
                [
                    'retry_after' => $retry_after,
                    'endpoint'    => $endpoint,
                    'retryable'   => true,
                ]
            );
        }
        
        // Client errors (4xx) - generally not retryable
        if ($status_code >= 400 && $status_code < 500) {
            $code_map = [
                400 => 'bad_request',
                401 => 'unauthorized',
                403 => 'forbidden',
                404 => 'not_found',
                405 => 'method_not_allowed',
                409 => 'conflict',
            ];
            
            $error_code = $code_map[$status_code] ?? 'client_error';
            $message = $data['errors'][0]['message'] ?? sprintf(__('HTTP %d error.', 'botfend-anti-bot-firewall'), $status_code);
            
            return new \WP_Error(
                $error_code,
                $message,
                [
                    'status_code' => $status_code,
                    'endpoint'    => $endpoint,
                    'retryable'   => false,
                ]
            );
        }
        
        // Server errors (5xx) - retryable
        if ($status_code >= 500) {
            $code_map = [
                500 => 'internal_server_error',
                502 => 'bad_gateway',
                503 => 'service_unavailable',
                504 => 'gateway_timeout',
            ];
            
            $error_code = $code_map[$status_code] ?? 'server_error';
            
            return new \WP_Error(
                $error_code,
                sprintf(__('Cloudflare server error (HTTP %d).', 'botfend-anti-bot-firewall'), $status_code),
                [                    'status_code' => $status_code,
                    'endpoint'    => $endpoint,
                    'retryable'   => true,
                ]
            );
        }
        
        return null;
    }

    /**
     * Handle Cloudflare API errors from response body.
     *
     * @since 4.9.0
     *
     * @param array  $data     Response data.
     * @param string $endpoint API endpoint.
     * @return \WP_Error
     */
    private function handle_api_error(array $data, string $endpoint): \WP_Error {
        $errors = $data['errors'] ?? [];
        $error_code = 'cf_api_error';
        $error_message = __('Unknown Cloudflare API error.', 'botfend-anti-bot-firewall');
        $retryable = false;
        
        if (!empty($errors) && is_array($errors)) {
            $first_error = $errors[0];
            
            if (isset($first_error['code'])) {
                $cf_code = $first_error['code'];
                $mapped = $this->map_cf_error_code($cf_code);
                $error_code = $mapped['code'];
                $retryable = $mapped['retryable'];
            }
            
            if (isset($first_error['message'])) {
                $error_message = $first_error['message'];
            }
        }
        
        return new \WP_Error(
            $error_code,
            $error_message,
            [
                'cf_errors' => $errors,
                'endpoint'  => $endpoint,
                'retryable' => $retryable,
            ]
        );
    }
    /**
     * Map Cloudflare error codes to internal codes with retry info.
     *
     * @since 4.9.0
     *
     * @param int $cf_code Cloudflare error code.
     * @return array Mapped code and retry flag.
     */
    private function map_cf_error_code(int $cf_code): array {
        // Cloudflare error codes: https://developers.cloudflare.com/api/reference/errors/
        $retryable_codes = [
            9109, // Rate limited
            9110, // Rate limited
        ];
        
        $code_map = [
            600  => ['code' => 'invalid_request', 'retryable' => false],
            601  => ['code' => 'unauthorized', 'retryable' => false],
            602  => ['code' => 'forbidden', 'retryable' => false],
            603  => ['code' => 'not_found', 'retryable' => false],
            700  => ['code' => 'invalid_json', 'retryable' => false],
            9109 => ['code' => 'rate_limited', 'retryable' => true],
            9110 => ['code' => 'rate_limited', 'retryable' => true],
            1001 => ['code' => 'invalid_dns', 'retryable' => false],
            1004 => ['code' => 'zone_not_found', 'retryable' => false],
        ];
        
        return $code_map[$cf_code] ?? [
            'code' => 'cf_api_error',
            'retryable' => in_array($cf_code, $retryable_codes, true),
        ];
    }

    /**
     * Get Retry-After header value.
     *
     * @since 4.9.0
     *
     * @param array $response Response array.
     * @return int Seconds to wait.
     */
    private function get_retry_after(array $response): int {
        $headers = wp_remote_retrieve_headers($response);
        
        if (is_array($headers) && isset($headers['retry-after'])) {
            $retry_after = $headers['retry-after'];
            
            // Could be seconds or HTTP date
            if (is_numeric($retry_after)) {                return max(1, (int) $retry_after);
            }
            
            // HTTP date format
            $timestamp = strtotime($retry_after);
            if ($timestamp !== false) {
                return max(1, $timestamp - time());
            }
        }
        
        // Default backoff
        return 60;
    }

    /**
     * Get request headers.
     *
     * @since 4.9.0
     *
     * @return array Headers array.
     */
    private function get_headers(): array {
        $auth_method = $this->config->get_string('cf_auth_method', 'token');
        
        $headers = [
            'Content-Type' => 'application/json',
            'User-Agent'   => $this->get_user_agent(),
        ];
        
        if ('token' === $auth_method) {
            $token = $this->get_decrypted_credential('cf_api_token');
            
            if (!empty($token)) {
                $headers['Authorization'] = 'Bearer ' . $token;
            }
        } else {
            $email = $this->config->get_string('cf_email', '');
            $key = $this->get_decrypted_credential('cf_api_key');
            
            if (!empty($email) && !empty($key)) {
                $headers['X-Auth-Email'] = sanitize_email($email);
                $headers['X-Auth-Key'] = $key;
            }
        }
        
        return $headers;
    }

    /**
     * Get User-Agent string.     *
     * @since 4.9.0
     *
     * @return string
     */
    private function get_user_agent(): string {
        $plugin_version = defined('BOTFEND_VERSION') ? BOTFEND_VERSION : '4.9.0';
        
        return sprintf(
            'BotFend/%s (WordPress/%s; PHP/%s; +%s)',
            $plugin_version,
            get_bloginfo('version'),
            PHP_VERSION,
            home_url()
        );
    }

    /**
     * Get decrypted credential value.
     *
     * @since 4.9.0
     *
     * @param string $key Option key.
     * @return string Decrypted value.
     */
    private function get_decrypted_credential(string $key): string {
        $value = $this->config->get_string($key, '');
        
        if (empty($value)) {
            return '';
        }
        
        if ($this->encryption->is_encrypted($value)) {
            $decrypted = $this->encryption->decrypt($value);
            return $decrypted !== false ? $decrypted : '';
        }
        
        return $value;
    }

    /**
     * Get zone ID with caching.
     *
     * @since 4.9.0
     *
     * @return string Zone ID or empty string.
     */
    private function get_zone_id(): string {
        if ($this->cached_zone_id !== null) {
            return $this->cached_zone_id;        }
        
        $this->cached_zone_id = $this->config->get_string('cf_zone_id', '');
        return $this->cached_zone_id;
    }

    /**
     * Validate zone ID is present.
     *
     * @since 4.9.0
     *
     * @return \WP_Error|null Error if invalid, null if valid.
     */
    private function validate_zone_id(): ?\WP_Error {
        $zone_id = $this->get_zone_id();
        
        if (empty($zone_id)) {
            return new \WP_Error(
                'missing_zone_id',
                __('Cloudflare Zone ID is not configured.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // Basic validation
        if (!preg_match('/^[a-f0-9]{32}$/i', $zone_id)) {
            return new \WP_Error(
                'invalid_zone_id',
                __('Invalid Cloudflare Zone ID format.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        return null;
    }

    /**
     * Log API request for debugging.
     *
     * @since 4.9.0
     *
     * @param string $method   HTTP method.
     * @param string $endpoint API endpoint.
     * @param ?array $body     Request body.
     * @return void
     */
    private function log_request(string $method, string $endpoint, ?array $body): void {
        if (!$this->should_log()) {
            return;
        }        
        $log_data = [
            'method'   => $method,
            'endpoint' => $endpoint,
        ];
        
        if ($body !== null) {
            // Sanitize sensitive data
            $log_data['body'] = $this->sanitize_log_data($body);
        }
        
        error_log(sprintf(
            '[BotFend CF API] Request: %s',
            wp_json_encode($log_data)
        ));
        
        do_action('botfend_cf_api_request', $method, $endpoint, $body);
    }

    /**
     * Log API response for debugging.
     *
     * @since 4.9.0
     *
     * @param string $method     HTTP method.
     * @param string $endpoint   API endpoint.
     * @param mixed  $status     Status code or error.
     * @param mixed  $response   Response data.
     * @return void
     */
    private function log_response(string $method, string $endpoint, $status, $response): void {
        if (!$this->should_log()) {
            return;
        }
        
        $log_data = [
            'method'   => $method,
            'endpoint' => $endpoint,
            'status'   => $status,
        ];
        
        if (is_array($response) && isset($response['success'])) {
            $log_data['success'] = $response['success'];
            
            if (!$response['success'] && isset($response['errors'])) {
                $log_data['errors'] = $response['errors'];
            }
        } elseif (is_string($response)) {
            $log_data['error'] = $response;
        }        
        error_log(sprintf(
            '[BotFend CF API] Response: %s',
            wp_json_encode($log_data)
        ));
        
        do_action('botfend_cf_api_response', $method, $endpoint, $status, $response);
    }

    /**
     * Check if logging is enabled.
     *
     * @since 4.9.0
     *
     * @return bool
     */
    private function should_log(): bool {
        return (defined('WP_DEBUG') && WP_DEBUG) ||
               (defined('BOTFEND_DEBUG') && BOTFEND_DEBUG) ||
               $this->config->get_bool('cf_api_debug', false);
    }

    /**
     * Sanitize data for logging (remove sensitive info).
     *
     * @since 4.9.0
     *
     * @param array $data Data to sanitize.
     * @return array Sanitized data.
     */
    private function sanitize_log_data(array $data): array {
        $sensitive_keys = ['token', 'key', 'password', 'secret', 'auth'];
        
        foreach ($data as $key => $value) {
            foreach ($sensitive_keys as $sensitive) {
                if (stripos($key, $sensitive) !== false) {
                    $data[$key] = '[REDACTED]';
                    continue 2;
                }
            }
            
            if (is_array($value)) {
                $data[$key] = $this->sanitize_log_data($value);
            }
        }
        
        return $data;
    }

    /**     * Check if Cloudflare API is configured.
     *
     * @since 4.9.0
     *
     * @return bool True if configured.
     */
    public function is_configured(): bool {
        $zone_id = $this->get_zone_id();
        
        if (empty($zone_id)) {
            return false;
        }
        
        $auth_method = $this->config->get_string('cf_auth_method', 'token');
        
        if ('token' === $auth_method) {
            $token = $this->get_decrypted_credential('cf_api_token');
            return !empty($token);
        }
        
        $email = $this->config->get_string('cf_email', '');
        $key = $this->get_decrypted_credential('cf_api_key');
        
        return !empty($email) && !empty($key);
    }

    /**
     * Test API connection.
     *
     * @since 4.9.0
     *
     * @return bool|\WP_Error True if connection successful.
     */
    public function test_connection() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $zone_id = $this->get_zone_id();
        $result = $this->request('GET', "zones/{$zone_id}");
        
        if (is_wp_error($result)) {
            return $result;
        }
        
        return true;
    }

    /**     * Validate IP address format.
     *
     * @since 4.9.0
     *
     * @param string $ip IP address.
     * @return bool True if valid.
     */
    public function validate_ip(string $ip): bool {
        // Trim whitespace
        $ip = trim($ip);
        
        // Validate IP format
        if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
            return false;
        }
        
        // Reject private/reserved IPs if configured
        if ($this->config->get_bool('cf_reject_private_ips', false)) {
            if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return false;
            }
        }
        
        return true;
    }

    /**
     * Create an empty firewall rule (no IPs yet).
     * Creates a filter first, then a rule using that filter.
     *
     * @since 4.9.0
     *
     * @param string $name   Rule name.
     * @param string $action Cloudflare action (block, challenge, js_challenge).
     * @return array|\WP_Error Response data.
     */
    public function create_empty_firewall_rule(string $name, string $action) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        // Validate action
        $valid_actions = ['block', 'challenge', 'js_challenge', 'managed_challenge', 'log', 'bypass'];
        if (!in_array($action, $valid_actions, true)) {
            $action = 'block';
        }
        
        // Sanitize name
        $name = substr(sanitize_text_field($name), 0, 255);        $zone_id = $this->get_zone_id();
        
        // STEP 1: Create a filter with placeholder expression
        $filter_body = [
            [
                'expression' => '(ip.src eq 0.0.0.0)',
                'description' => $name . ' - Filter',
                'paused' => true
            ]
        ];
        
        $filter_response = $this->request('POST', "zones/{$zone_id}/filters", $filter_body);
        
        if (is_wp_error($filter_response)) {
            return $filter_response;
        }
        
        // Get the filter ID from response
        $filter_id = $filter_response['result'][0]['id'] ?? '';
        
        if (empty($filter_id)) {
            return new \WP_Error(
                'no_filter_id',
                __('No filter ID returned from Cloudflare.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // STEP 2: Create a rule using the filter ID - FIXED: Create rule as active immediately
        $rule_body = [
            [
                'description' => $name,
                'action' => $action,
                'filter' => ['id' => $filter_id],
                'paused' => false  // FIXED: Start as active instead of paused
            ]
        ];
        
        $rule_response = $this->request('POST', "zones/{$zone_id}/firewall/rules", $rule_body);
        
        if (is_wp_error($rule_response)) {
            // Clean up the filter if rule creation fails
            $this->request('DELETE', "zones/{$zone_id}/filters/{$filter_id}");
            return $rule_response;
        }
        
        // Return the first rule result
        return [
            'success' => true,
            'result' => $rule_response['result'][0],            'result_info' => $rule_response['result_info'] ?? []
        ];
    }

    /**
     * Update firewall rule expression with new IP list.
     * Updates the filter associated with the rule.
     *
     * @since 4.9.0
     *
     * @param string $rule_id    Cloudflare rule ID.
     * @param string $expression New expression (e.g., "(ip.src eq IP1) or (ip.src eq IP2)").
     * @return array|\WP_Error Response data.
     */
    public function update_firewall_rule_expression(string $rule_id, string $expression) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        // Validate rule ID format
        if (!preg_match('/^[a-f0-9]{32}$/i', $rule_id)) {
            return new \WP_Error(
                'invalid_rule_id',
                __('Invalid rule ID format.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // Validate expression length
        if (strlen($expression) > 4096) {
            return new \WP_Error(
                'expression_too_long',
                sprintf(
                    __('Expression exceeds maximum length (%d chars).', 'botfend-anti-bot-firewall'),
                    strlen($expression)
                ),
                ['retryable' => false, 'length' => strlen($expression)]
            );
        }
        
        $zone_id = $this->get_zone_id();
        
        // STEP 1: Get the current rule to find its filter ID
        $current_rule = $this->request('GET', "zones/{$zone_id}/firewall/rules/{$rule_id}");
        
        if (is_wp_error($current_rule)) {
            return $current_rule;
        }
                $filter_id = $current_rule['result']['filter']['id'] ?? '';
        
        if (empty($filter_id)) {
            return new \WP_Error(
                'no_filter',
                __('No filter ID found for rule.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // STEP 2: Update the filter with new expression
        $filter_body = [
            'expression' => $expression,
            'paused' => false
        ];
        
        $filter_response = $this->request('PUT', "zones/{$zone_id}/filters/{$filter_id}", $filter_body);
        
        if (is_wp_error($filter_response)) {
            return $filter_response;
        }
        
        // STEP 3: Ensure the rule is not paused - FIXED: Only update if needed
        $rule_body = ['paused' => false];
        
        return $this->request('PATCH', "zones/{$zone_id}/firewall/rules/{$rule_id}", $rule_body);
    }

    /**
     * Create an IP filter (single IP).
     *
     * @since 4.9.0
     *
     * @param string $ip          IP address.
     * @param string $description Filter description.
     * @return array|\WP_Error Response data.
     */
    public function create_ip_filter(string $ip, string $description) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (!$this->validate_ip($ip)) {
            return new \WP_Error(
                'invalid_ip',
                __('Invalid IP address.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }        
        $zone_id = $this->get_zone_id();
        
        $body = [
            [
                'expression'  => sprintf('(ip.src eq %s)', $ip),
                'description' => substr(sanitize_text_field($description), 0, 500),
            ],
        ];
        
        return $this->request('POST', "zones/{$zone_id}/filters", $body);
    }

    /**
     * Create a firewall rule from a filter.
     *
     * @since 4.9.0
     *
     * @param string $filter_id   Filter ID.
     * @param string $action      Cloudflare action.
     * @param string $description Rule description.
     * @return array|\WP_Error Response data.
     */
    public function create_firewall_rule(string $filter_id, string $action, string $description) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($filter_id)) {
            return new \WP_Error(
                'empty_filter_id',
                __('Filter ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        
        $body = [
            [
                'filter'      => ['id' => $filter_id],
                'action'      => $action,
                'description' => substr(sanitize_text_field($description), 0, 500),
                'paused'      => false  // FIXED: Ensure rule starts as active
            ]
        ];
        
        return $this->request('POST', "zones/{$zone_id}/firewall/rules", $body);
    }
    /**
     * Delete a firewall rule and its associated filter.
     *
     * @since 4.9.0
     *
     * @param string $rule_id Cloudflare rule ID.
     * @return array|\WP_Error Response data.
     */
    public function delete_firewall_rule(string $rule_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($rule_id)) {
            return new \WP_Error(
                'empty_rule_id',
                __('Rule ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        
        // Get rule to find filter
        $rule = $this->request('GET', "zones/{$zone_id}/firewall/rules/{$rule_id}");
        
        $filter_id = null;
        if (!is_wp_error($rule) && isset($rule['result']['filter']['id'])) {
            $filter_id = $rule['result']['filter']['id'];
        }
        
        // Delete rule
        $result = $this->request('DELETE', "zones/{$zone_id}/firewall/rules/{$rule_id}");
        
        // Delete filter if exists
        if ($filter_id && !is_wp_error($result)) {
            $this->request('DELETE', "zones/{$zone_id}/filters/{$filter_id}");
        }
        
        return $result;
    }

    /**
     * Delete a filter.
     *
     * @since 4.9.0
     *
     * @param string $filter_id Filter ID.     * @return array|\WP_Error Response data.
     */
    public function delete_filter(string $filter_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($filter_id)) {
            return new \WP_Error(
                'empty_filter_id',
                __('Filter ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('DELETE', "zones/{$zone_id}/filters/{$filter_id}");
    }

    /**
     * Purge all cache for the zone.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function purge_all_cache() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('POST', "zones/{$zone_id}/purge_cache", ['purge_everything' => true]);
    }

    /**
     * Get zone settings.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_zone_settings() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
                $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/settings");
    }

    /**
     * Get firewall rules.
     *
     * @since 4.9.0
     *
     * @param int $page     Page number.
     * @param int $per_page Items per page.
     * @return array|\WP_Error Response data.
     */
    public function get_firewall_rules(int $page = 1, int $per_page = 50) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $page = max(1, $page);
        $per_page = max(1, min(100, $per_page));
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/firewall/rules?page={$page}&per_page={$per_page}");
    }

    /**
     * Get filters.
     *
     * @since 4.9.0
     *
     * @param int $page     Page number.
     * @param int $per_page Items per page.
     * @return array|\WP_Error Response data.
     */
    public function get_filters(int $page = 1, int $per_page = 50) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $page = max(1, $page);
        $per_page = max(1, min(100, $per_page));
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/filters?page={$page}&per_page={$per_page}");
    }

    /**
     * Get a specific firewall rule.     *
     * @since 4.9.0
     *
     * @param string $rule_id Rule ID.
     * @return array|\WP_Error Response data.
     */
    public function get_firewall_rule(string $rule_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($rule_id)) {
            return new \WP_Error(
                'empty_rule_id',
                __('Rule ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/firewall/rules/{$rule_id}");
    }

    /**
     * Get a specific filter.
     *
     * @since 4.9.0
     *
     * @param string $filter_id Filter ID.
     * @return array|\WP_Error Response data.
     */
    public function get_filter(string $filter_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($filter_id)) {
            return new \WP_Error(
                'empty_filter_id',
                __('Filter ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/filters/{$filter_id}");
    }
    /**
     * Create multiple IP filters in batch.
     *
     * @since 4.9.0
     *
     * @param array $ips Array of IPs with descriptions.
     * @return array|\WP_Error Response data.
     */
    public function create_ip_filters_batch(array $ips) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($ips) || !is_array($ips)) {
            return new \WP_Error(
                'invalid_ips',
                __('IPs array is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // Cloudflare batch limit
        if (count($ips) > 100) {
            return new \WP_Error(
                'batch_too_large',
                __('Maximum 100 filters per batch.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        $body = [];
        
        foreach ($ips as $ip_data) {
            if (!isset($ip_data['ip']) || !$this->validate_ip($ip_data['ip'])) {
                continue; // Skip invalid IPs
            }
            
            $body[] = [
                'expression'  => sprintf('(ip.src eq %s)', $ip_data['ip']),
                'description' => substr(sanitize_text_field($ip_data['description'] ?? ''), 0, 500),
            ];
        }
        
        if (empty($body)) {
            return new \WP_Error(
                'no_valid_ips',
                __('No valid IPs in batch.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]            );
        }
        
        return $this->request('POST', "zones/{$zone_id}/filters", $body);
    }

    /**
     * Delete multiple filters in batch.
     *
     * @since 4.9.0
     *
     * @param array $filter_ids Array of filter IDs.
     * @return array|\WP_Error Response data.
     */
    public function delete_filters_batch(array $filter_ids) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($filter_ids) || !is_array($filter_ids)) {
            return new \WP_Error(
                'invalid_filter_ids',
                __('Filter IDs array is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        // Validate filter IDs
        $valid_ids = array_filter($filter_ids, function($id) {
            return !empty($id) && preg_match('/^[a-f0-9]{32}$/i', $id);
        });
        
        if (empty($valid_ids)) {
            return new \WP_Error(
                'no_valid_filter_ids',
                __('No valid filter IDs.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('DELETE', "zones/{$zone_id}/filters", array_values($valid_ids));
    }

    /**
     * Update multiple firewall rules in batch.
     *
     * @since 4.9.0
     *     * @param array $rules Array of rule updates.
     * @return array|\WP_Error Response data.
     */
    public function update_firewall_rules_batch(array $rules) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($rules) || !is_array($rules)) {
            return new \WP_Error(
                'invalid_rules',
                __('Rules array is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        $body = [];
        
        foreach ($rules as $rule) {
            if (!isset($rule['id']) || empty($rule['id'])) {
                continue;
            }
            
            $body[] = [
                'id'     => $rule['id'],
                'paused' => !empty($rule['paused']),
            ];
        }
        
        if (empty($body)) {
            return new \WP_Error(
                'no_valid_rules',
                __('No valid rules in batch.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        return $this->request('PATCH', "zones/{$zone_id}/firewall/rules", $body);
    }

    /**
     * Get user information.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_user_info() {        return $this->request('GET', 'user');
    }

    /**
     * Get zones.
     *
     * @since 4.9.0
     *
     * @param string $name Optional zone name filter.
     * @return array|\WP_Error Response data.
     */
    public function get_zones(string $name = '') {
        $endpoint = 'zones';
        
        if (!empty($name)) {
            $endpoint .= '?name=' . urlencode(sanitize_text_field($name));
        }
        
        return $this->request('GET', $endpoint);
    }

    /**
     * Verify zone access.
     *
     * @since 4.9.0
     *
     * @param string $zone_id Zone ID to verify.
     * @return bool True if accessible.
     */
    public function verify_zone_access(string $zone_id): bool {
        if (empty($zone_id) || !preg_match('/^[a-f0-9]{32}$/i', $zone_id)) {
            return false;
        }
        
        $result = $this->request('GET', "zones/{$zone_id}");
        return !is_wp_error($result) && isset($result['success']) && $result['success'] === true;
    }

    /**
     * Get rate limits.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_rate_limits() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/rate_limits");
    }

    /**
     * Create rate limit rule.
     *
     * @since 4.9.0
     *
     * @param array $config Rate limit configuration.
     * @return array|\WP_Error Response data.
     */
    public function create_rate_limit(array $config) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($config)) {
            return new \WP_Error(
                'invalid_config',
                __('Rate limit configuration is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('POST', "zones/{$zone_id}/rate_limits", $config);
    }

    /**
     * Delete rate limit rule.
     *
     * @since 4.9.0
     *
     * @param string $rate_limit_id Rate limit ID.
     * @return array|\WP_Error Response data.
     */
    public function delete_rate_limit(string $rate_limit_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($rate_limit_id)) {
            return new \WP_Error(
                'empty_rate_limit_id',
                __('Rate limit ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('DELETE', "zones/{$zone_id}/rate_limits/{$rate_limit_id}");
    }

    /**
     * Get security level.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_security_level() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/settings/security_level");
    }

    /**
     * Update security level.
     *
     * @since 4.9.0
     *
     * @param string $level Security level.
     * @return array|\WP_Error Response data.
     */
    public function update_security_level(string $level) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $valid_levels = ['off', 'essentially_off', 'low', 'medium', 'high', 'under_attack'];
        
        if (!in_array($level, $valid_levels, true)) {
            return new \WP_Error(
                'invalid_security_level',
                sprintf(__('Invalid security level: %s', 'botfend-anti-bot-firewall'), $level),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('PATCH', "zones/{$zone_id}/settings/security_level", ['value' => $level]);    }

    /**
     * Get challenge TTL.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_challenge_passage() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/settings/challenge_ttl");
    }

    /**
     * Set challenge TTL.
     *
     * @since 4.9.0
     *
     * @param int $seconds TTL in seconds (300-86400).
     * @return array|\WP_Error Response data.
     */
    public function set_challenge_passage(int $seconds) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $seconds = max(300, min(86400, $seconds));
        
        $zone_id = $this->get_zone_id();
        return $this->request('PATCH', "zones/{$zone_id}/settings/challenge_ttl", ['value' => $seconds]);
    }

    /**
     * Get bot management settings.
     *
     * @since 4.9.0
     *
     * @return array|\WP_Error Response data.
     */
    public function get_bot_management() {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;        }
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/bot_management");
    }

    /**
     * Get IP access rules.
     *
     * @since 4.9.0
     *
     * @param int $page     Page number.
     * @param int $per_page Items per page.
     * @return array|\WP_Error Response data.
     */
    public function get_ip_access_rules(int $page = 1, int $per_page = 50) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        $page = max(1, $page);
        $per_page = max(1, min(100, $per_page));
        
        $zone_id = $this->get_zone_id();
        return $this->request('GET', "zones/{$zone_id}/firewall/access_rules/rules?page={$page}&per_page={$per_page}");
    }

    /**
     * Create IP access rule.
     *
     * @since 4.9.0
     *
     * @param string $ip    IP address or range.
     * @param string $mode  Action.
     * @param string $notes Notes.
     * @return array|\WP_Error Response data.
     */
    public function create_ip_access_rule(string $ip, string $mode, string $notes = '') {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (!$this->validate_ip($ip)) {
            return new \WP_Error(
                'invalid_ip',
                __('Invalid IP address.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );        }
        
        $valid_modes = ['block', 'challenge', 'whitelist', 'js_challenge', 'managed_challenge'];
        
        if (!in_array($mode, $valid_modes, true)) {
            return new \WP_Error(
                'invalid_mode',
                sprintf(__('Invalid mode: %s', 'botfend-anti-bot-firewall'), $mode),
                ['retryable' => false]
            );
        }
        
        $zone_id = $this->get_zone_id();
        
        $body = [
            'mode' => $mode,
            'configuration' => [
                'target' => 'ip',
                'value'  => $ip,
            ],
        ];
        
        if (!empty($notes)) {
            $body['notes'] = substr(sanitize_textarea_field($notes), 0, 500);
        }
        
        return $this->request('POST', "zones/{$zone_id}/firewall/access_rules/rules", $body);
    }

    /**
     * Delete IP access rule.
     *
     * @since 4.9.0
     *
     * @param string $rule_id Access rule ID.
     * @return array|\WP_Error Response data.
     */
    public function delete_ip_access_rule(string $rule_id) {
        $zone_error = $this->validate_zone_id();
        if (is_wp_error($zone_error)) {
            return $zone_error;
        }
        
        if (empty($rule_id)) {
            return new \WP_Error(
                'empty_rule_id',
                __('Rule ID is required.', 'botfend-anti-bot-firewall'),
                ['retryable' => false]
            );
        }        
        $zone_id = $this->get_zone_id();
        return $this->request('DELETE', "zones/{$zone_id}/firewall/access_rules/rules/{$rule_id}");
    }
}