<?php
/**
 * Newton Security API Client
 * 
 * Handles communication with the Newton Security Django API backend.
 * Implements authentication, firewall sync, scanner integration, and threat intelligence.
 *
 * @package VMPFence
 * @since 2.0.0
 * @since 2.2.0 - Added export/import config endpoints, enhanced logging, and improved error handling
 * @since 2.2.2 - Removed annonymouse API key method
 * @since 2.2.4 - Added SIEM command endpoints, Sync Methods
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * VMPFence API Client Class
 *
 * @since 1.0.0
 */
class VMPFence_API_Client {

    /**
     * API base URL
     *
     * @var string
     */
    private $api_base_url;

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

    /**
     * Maximum retry attempts
     *
     * @var int
     */
    private $max_retries = 3;

    /**
     * Enable debug logging
     *
     * @var bool
     */
    private $debug = false;

    /**
     * Constructor
     *
     * @since 1.0.0
     */
    public function __construct() {
        // Get dynamic API base URL
        $this->api_base_url = VMPFence_Endpoint_Config::get_api_base_url();
        $this->debug = defined('WP_DEBUG') && WP_DEBUG;
    }

    // ============================================================================
    // AUTHENTICATION METHODS
    // ============================================================================

    /**
     * Validate API key
     *
     * @since 1.0.0
     * @since 2.2.3 - Added plugin, wp, php version info for better diagnostics
     * @param string $api_key The API key to validate
     * @return array|WP_Error Validation response or error
     */
    public function check_api_key($api_key) {
        global $wp_version;
        
        $data = array(
            'api_key' => $api_key,
            'plugin_version' => VMPFENCE_VERSION,
            'wp_version' => $wp_version,
            'php_version' => phpversion(),
        );

        $response = $this->make_request('auth/check_api_key', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('API key validation failed: ' . $response->get_error_message(), 'error');
            return $response;
        }

        $this->log('API key validated successfully', 'info');
        return $response;
    }

    /**
     * Ping API to check connectivity
     *
     * @since 1.0.0
     * @param string $api_key The API key
     * @return array|WP_Error Ping response or error
     */
    public function ping_api_key($api_key) {
        // Django expects api_key in body as { "api_key": "..." }
        $data = array(
            'api_key' => $api_key,
        );

        $response = $this->make_request('auth/ping_api_key', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('API ping failed: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Activate a license key
     *
     * @since 1.0.0
     * @since 2.2.2 - Clear manual deactivation flag on successful activation
     * @since 2.2.3 - Added plugin, wp, php version info for better diagnostics
     * @since 2.2.4 - Use activation response data directly to update license settings without extra API call
     * @param string $license_key License key to activate
     * @param string $site_url WordPress site URL
     * @param string $email Optional email address (uses admin_email if not provided)
     * @return array|WP_Error Activation response or error
     */
    public function activate_license($license_key, $site_url, $email = '') {
        global $wp_version;
        
        // For license activation, we send the license_key, email and site_url
        // The server will validate and activate the premium license
        // Use provided email or fallback to admin_email
        if (empty($email)) {
            $email = get_option('admin_email');
        }
        
        $data = array(
            'license_key' => $license_key,  // Premium license key to activate
            'email' => $email,  // Admin email for user identification
            'site_url' => $site_url,
            'source' => 'plugin',  // Source identifier for server validation
            'plugin_version' => VMPFENCE_VERSION,
            'wp_version' => $wp_version,
            'php_version' => phpversion(),
        );

        $response = $this->make_request('auth/activate_license', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('License activation failed: ' . $response->get_error_message(), 'error');
            return $response;
        }

        if (isset($response['success']) && $response['success']) {
            $this->log('License activated successfully: ' . $response['license_type'], 'info');
            
            // CRITICAL: Clear manual deactivation flag since user is now activating
            if (class_exists('VMPFence_Config')) {
                VMPFence_Config::delete('license_manually_deactivated');
            }
            
            // NOTE: License email and other config will be set by the calling code (setup wizard)
            // to ensure consistency with form data
            
            // ✅ PERFORMANCE FIX: Use activation response data directly instead of making extra API call
            // The activation response already contains all the license data we need
            if (isset($response['license_key']) && !empty($response['license_key'])) {
                VMPFence_License::update_license_settings(array(
                    'license_type' => $response['license_type'] ?? 'free',
                    'license_key' => $response['license_key'],
                    'expires_at' => $response['expires_at'] ?? null,
                    'max_activations' => $response['max_activations'] ?? 1,
                ));
            }
        }

        return $response;
    }

    /**
     * Deactivate a license key
     *
     * @since 1.0.0
     * @param string $license_key License key to deactivate
     * @param string $site_url WordPress site URL
     * @return array|WP_Error Deactivation response or error
     */
    public function deactivate_license($license_key, $site_url) {
        // Get the email from license settings, or fallback to admin email
        $email = VMPFence_Config::get('license_email', '');
        
        if (empty($email)) {
            // Fallback to WordPress admin email (same as activation)
            $email = get_option('admin_email');
            
            if (empty($email)) {
                $this->log('Cannot deactivate license: email not found', 'error');
                return new WP_Error('missing_email', 'Email address not found.');
            }
        }
        
        $this->log('Deactivating license for email: ' . $email . ', site: ' . $site_url, 'info');
        
        $data = array(
            'email' => $email,
            'site_url' => $site_url,
        );

        $response = $this->make_request('auth/deactivate_license', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('License deactivation failed: ' . $response->get_error_message(), 'error');
            return $response;
        }

        if (isset($response['success']) && $response['success']) {
            $this->log('License deactivated successfully', 'info');
            
            // Update local storage to reflect free tier
            VMPFence_License::update_license_settings(array(
                'license_type' => 'free',
                'license_key' => $response['new_free_license_key'] ?? '',
                'expires_at' => null,
                'max_activations' => 1,
            ));
            
            // Clear the stored email after deactivation - user may activate with a different email/license
            VMPFence_Config::delete('license_email');
        }
        
        return $response;
    }

    /**
     * Get or create API key for this site
     *
     * Called during plugin activation to retrieve existing API key or create new user.
     * This prevents duplicate user creation on plugin reinstall.
     * Server is the single source of truth for site identity.
     *
     * NOTE: This endpoint should work even if endpoint mapper is unavailable
     * (plugin activation may happen before endpoint config is fetched).
     * The endpoint path will fall back to logical name if mapper fails.
     *
     * @since 2.2.3
     * @param string $site_url WordPress site URL (unique identifier)
     * @param string $email Optional admin email (used for new user creation)
     * @return array|WP_Error Response with API key details or error
     */
    public function get_or_create_api_key($site_url, $email = '') {
        if (empty($email)) {
            $email = get_option('admin_email', '');
        }

        $data = array(
            'site_url' => $site_url,
            'email' => $email
        );

        $response = $this->make_request('auth/get-or-create-api-key', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get/create API key: ' . $response->get_error_message(), 'error');
            return $response;
        }

        if (isset($response['success']) && $response['success']) {
            $action = isset($response['is_new']) && $response['is_new'] === true ? 'created' : 'retrieved';
            $user_id = isset($response['user_id']) ? $response['user_id'] : 'unknown';
            $this->log('API key ' . $action . ' successfully (User ID: ' . $user_id . ')', 'info');
        }

        return $response;
    }

    /**
     * Get license status
     *
     * @since 1.0.0
     * @param string $license_key License key to check
     * @return array|WP_Error License status or error
     */
    public function get_license_status($license_key) {
        $response = $this->make_request('auth/license_status/' . urlencode($license_key), 'GET');

        if (is_wp_error($response)) {
            $this->log('Failed to get license status: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    // ============================================================================
    // CORE HTTP REQUEST METHOD
    // ============================================================================

    /**
     * Make HTTP request to API
     *
     * @since 1.0.0
     * @since 2.2.2 - Enhanced logging and error handling for better diagnostics
     * @param string $endpoint API endpoint identifier
     * @param string $method HTTP method (GET, POST, PUT, DELETE)
     * @param array $data Request data
     * @param array $headers Additional headers
     * @return array|WP_Error Response data or error
     */
    private function make_request($endpoint, $method = 'GET', $data = array(), $headers = array()) {
        // IMPORTANT: Check endpoint BEFORE mapping for API key handling
        // We need to know the logical endpoint to determine where api_key goes
        $logical_endpoint = $endpoint; // Store original for detection
        
        // Resolve endpoint using secure mapping (Phase 13.4 - Security Enhancement)
        $resolved_endpoint = VMPFence_Endpoint_Mapper::get_endpoint($endpoint);
        $url = $this->api_base_url . '/' . ltrim($resolved_endpoint, '/');
        
        $start_time = microtime(true);

        $this->log("Making {$method} request to: {$endpoint}", 'info');
        $this->log("Endpoint resolved: {$endpoint} -> {$resolved_endpoint}", 'debug');
        $this->log("Full URL: {$url}", 'debug');        // Build request arguments
        $args = array(
            'method' => strtoupper($method),
            'timeout' => $this->timeout,
            'redirection' => 5,
            'httpversion' => '1.1',
            'user-agent' => $this->get_user_agent(),
            'headers' => array_merge(
                array(
                    'Content-Type' => 'application/json',
                    'Accept' => 'application/json',
                ),
                $headers
            ),
            'sslverify' => true,
        );

        // Extract api_key for query string if present (except for endpoints that need it in body)
        $api_key_param = array();
        
        // Determine if this endpoint needs api_key in body vs query string
        // Check the LOGICAL endpoint name (before mapping) to determine API key placement
        $endpoint_hash = substr(md5($logical_endpoint), 0, 8);
        
        // Hash identifiers for endpoints requiring api_key in body
        // (Precomputed hashes - no visible endpoint paths)
        $body_key_hashes = array(
            'bd76c3f2', // auth/check_api_key
            'd83ef452', // auth/ping_api_key
            '4c96ecaa', // auth/register_site
            '8c7f517a', // auth/deactivate_key
            '8c870a21', // auth/activate_license
            '21962086', // auth/deactivate_license
            '8168473e', // auth/validate_license
            '6fd01091', // login/setup-2fa
            '231692a2', // login/verify-2fa
            'aa885c79', // login/disable-2fa
            '839b4ad9', // login/verify-2fa-setup
            'e7b8851a', // login/2fa-status
            '365e9ab4', // login/regenerate-backup-codes
            '1e009e81', // login/verify-backup-code
            '476d709b', // login/backup-codes/generate
            '02159d7f', // login/backup-codes/verify
            '18c8cb5b', // login/login-attempts
            'ce796c27', // login/attempt
            '95b6503c', // plugin/check-update
            '047b8659', // config/export
        );
        
        $needs_body_key = in_array($endpoint_hash, $body_key_hashes);
        
        if (isset($data['api_key']) && !$needs_body_key) {
            $api_key_param['api_key'] = $data['api_key'];
            unset($data['api_key']); // Remove from body data for endpoints using query params
        }

        // Add api_key to URL as query parameter (for non-auth endpoints)
        if (!empty($api_key_param)) {
            $url = add_query_arg($api_key_param, $url);
        }

        // Add body for POST/PUT/PATCH/DELETE
        if (in_array($args['method'], array('POST', 'PUT', 'PATCH', 'DELETE')) && !empty($data)) {
            $args['body'] = wp_json_encode($data);
        }

        // For GET requests, add data as query parameters
        if ($args['method'] === 'GET' && !empty($data)) {
            $url = add_query_arg($data, $url);
        }

        // Retry logic with exponential backoff
        $attempt = 0;
        $backoff = 1; // seconds
        $last_error = null;

        while ($attempt < $this->max_retries) {
            $response = wp_remote_request($url, $args);
            
            // Check for successful response
            if (!is_wp_error($response)) {
                $elapsed = round((microtime(true) - $start_time) * 1000, 2);
                $this->log("Request completed in {$elapsed}ms", 'debug');
                
                return $this->parse_response($response);
            }

            // Store error for diagnostic logging
            $last_error = $response;
            $error_code = $response->get_error_code();
            $error_message = $response->get_error_message();
            
            // Log detailed error on first attempt for diagnostics
            if ($attempt === 0) {
                $this->log("Request error details - Code: {$error_code}, Message: {$error_message}", 'error');
                
                // Log PHP configuration that might affect HTTP requests
                if (class_exists('VMPFence_Secure_Logger')) {
                    VMPFence_Secure_Logger::log('error', 'HTTP Request Failed - Diagnostics', array(
                        'url' => parse_url($url, PHP_URL_HOST), // Don't log full URL (may contain API key)
                        'error_code' => $error_code,
                        'error_message' => $error_message,
                        'allow_url_fopen' => ini_get('allow_url_fopen'),
                        'curl_available' => function_exists('curl_version'),
                        'openssl_available' => extension_loaded('openssl'),
                        'timeout' => $this->timeout,
                        'endpoint' => $endpoint
                    ));
                }
            }

            $attempt++;
            
            if ($attempt < $this->max_retries) {
                $this->log("Request failed, retrying in {$backoff}s (attempt {$attempt}/{$this->max_retries})", 'warning');
                sleep($backoff);
                $backoff *= 2; // Exponential backoff
            }
        }

        // All retries failed - provide detailed error
        $error_message = $last_error ? $last_error->get_error_message() : 'Unknown error';
        $error_code = $last_error ? $last_error->get_error_code() : 'unknown';
        
        $this->log("Request failed after {$this->max_retries} attempts: {$error_message}", 'error');

        // Track connection failure for diagnostics (reported to server on next successful identify)
        self::record_connection_failure($endpoint, $error_code, $error_message);
        
        // Provide more helpful error messages based on error code
        $user_message = $error_message;
        if (strpos($error_code, 'http_request_failed') !== false) {
            $user_message = __('Unable to connect to Newton Security API. Your server may be blocking outbound HTTPS connections. Please contact your hosting provider.', 'vmpfence-security');
        } elseif (strpos($error_message, 'cURL error 60') !== false || strpos($error_message, 'SSL') !== false) {
            $user_message = __('SSL certificate verification failed. This is usually a server configuration issue. Contact your hosting provider.', 'vmpfence-security');
        } elseif (strpos($error_message, 'timed out') !== false || strpos($error_message, 'timeout') !== false) {
            $user_message = __('Request timed out. Your server may have slow outbound connections. Try again later.', 'vmpfence-security');
        }
        
        return new WP_Error(
            'api_request_failed',
            $user_message,
            array(
                'endpoint' => $endpoint,
                'error_code' => $error_code,
                'technical_error' => $error_message,
                'attempts' => $this->max_retries
            )
        );
    }

    /**
     * Parse API response
     *
     * @since 1.0.0
     * @param array $response WordPress HTTP response
     * @return array|WP_Error Parsed response data or error
     */
    private function parse_response($response) {
        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);

        $this->log("Response status: {$status_code}", 'debug');

        // Parse JSON response
        $data = json_decode($body, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->log('Failed to parse JSON response: ' . json_last_error_msg() . ' | Body preview: ' . substr($body, 0, 200), 'error');
            return new WP_Error(
                'json_parse_error',
                __('Invalid JSON response from API', 'vmpfence-security'),
                array('body' => $body)
            );
        }

        // Handle HTTP error status codes
        if ($status_code >= 400) {
            $error_message = isset($data['error']) ? $data['error'] : __('Unknown error', 'vmpfence-security');
            $error_code = isset($data['code']) ? $data['code'] : $status_code;

            // Log detailed error information
            $this->log("API error ({$error_code}): {$error_message}", 'error');
            
            // Log validation errors without exposing sensitive details
            if ($status_code == 422 && isset($data['detail'])) {
                VMPFence_Secure_Logger::error('API validation error', ['status' => $status_code, 'detail' => $data['detail']]);
            }
            
            // Log full response body only in development (not production)
            if (defined('WP_DEBUG') && WP_DEBUG && !VMPFence_Secure_Logger::is_production()) {
                $this->log("Full error response: " . $body, 'error');
            }

            return new WP_Error(
                'api_error_' . $error_code,
                $error_message,
                array(
                    'status_code' => $status_code,
                    'data' => $data,
                    'body' => $body
                )
            );
        }

        return $data;
    }

    /**
     * Get User-Agent string for API requests
     *
     * @since 1.0.0
     * @return string User-Agent header value
     */
    private function get_user_agent() {
        return sprintf(
            'VMPFence/%s; WordPress/%s; %s',
            VMPFENCE_VERSION,
            get_bloginfo('version'),
            home_url()
        );
    }

    /**
     * Log message
     *
     * @since 1.0.0
     * @param string $message Log message
     * @param string $level Log level (debug, info, warning, error)
     * @return void
     */
    private function log($message, $level = 'info') {
        if (!$this->debug && $level === 'debug') {
            return;
        }

        // Use secure logger which automatically sanitizes sensitive data
        if (class_exists('VMPFence_Secure_Logger')) {
            $formatted_message = sprintf('[API Client] %s', $message);
            VMPFence_Secure_Logger::log($level, $formatted_message);
        }
        // If secure logger is not available, fail silently in production
        // This prevents accidental credential/data leaks via error_log
    }

    /**
     * Set API base URL (for testing)
     *
     * @since 1.0.0
     * @param string $url New base URL
     * @return void
     */
    public function set_api_url($url) {
        $this->api_base_url = rtrim($url, '/');
    }

    /**
     * Set request timeout
     *
     * @since 1.0.0
     * @param int $timeout Timeout in seconds
     * @return void
     */
    public function set_timeout($timeout) {
        $this->timeout = max(5, intval($timeout));
    }

    /**
     * Enable or disable debug logging
     *
     * @since 1.0.0
     * @param bool $debug Enable debug mode
     * @return void
     */
    public function set_debug($debug) {
        $this->debug = (bool) $debug;
    }

    // =============================================================================
    // CONNECTION HEALTH TRACKING
    // =============================================================================

    /**
     * Record a connection failure for diagnostics reporting.
     * Stored in a transient (24h TTL) and sent to server on next successful identify.
     *
     * @since 2.2.6
     * @param string $endpoint The endpoint that failed
     * @param string $error_code Error code from WP_Error
     * @param string $error_message Human-readable error message
     * @return void
     */
    public static function record_connection_failure($endpoint, $error_code, $error_message) {
        $health = get_transient('vmpfence_connection_health');
        if (!is_array($health)) {
            $health = array('failure_count' => 0);
        }

        $health['failure_count'] = ($health['failure_count'] ?? 0) + 1;
        $health['last_error'] = substr($error_message, 0, 200);
        $health['last_error_code'] = $error_code;
        $health['last_error_time'] = gmdate('c');
        $health['last_failed_endpoint'] = $endpoint;

        set_transient('vmpfence_connection_health', $health, DAY_IN_SECONDS);
    }

    /**
     * Build connection_health payload for the identify call.
     * Includes failure data + WP-Cron status.
     * Resets failure count after building (so it's only reported once).
     *
     * @since 2.2.6
     * @return array Connection health data
     */
    public static function get_connection_health_payload() {
        $health = get_transient('vmpfence_connection_health');
        if (!is_array($health)) {
            $health = array();
        }

        // Add WP-Cron status
        $health['cron_status'] = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? 'disabled' : 'enabled';

        // Add last sync time from the sync manager
        $last_sync = get_option('vmpfence_siem_last_sync', array());
        $health['last_sync_time'] = $last_sync['time_iso'] ?? null;

        // Reset failure count after reporting
        if (!empty($health['failure_count'])) {
            $reset = $health;
            $reset['failure_count'] = 0;
            set_transient('vmpfence_connection_health', $reset, DAY_IN_SECONDS);
        }

        return $health;
    }
    
    // =============================================================================
    // FIREWALL ENDPOINTS
    // =============================================================================
    
    /**
     * Get firewall rules from API (tiered by license)
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @return array|WP_Error Array of rules on success, WP_Error on failure
     */
    public function get_firewall_rules($api_key) {
        $endpoint = 'firewall/rules?api_key=' . urlencode($api_key);
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch firewall rules: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response format: {rules: [...], total_count: N, license_tier: "free|premium"}
        return $response;
    }
    
    /**
     * Upload custom rule to API server
     *
     * @since 1.0.0
     * @param array $rule_data Rule data (rule_id, category, severity, pattern, etc.)
     * @param string $api_key API key for authentication
     * @return array|WP_Error Response on success, WP_Error on failure
     */
    public function upload_custom_rule($rule_data, $api_key) {
        $endpoint = 'firewall/admin/import-rules?api_key=' . urlencode($api_key);
        
        // Wrap single rule in array format expected by API
        $payload = array(
            'rules' => array($rule_data),
            'skip_duplicates' => true
        );
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        if (is_wp_error($response)) {
            $this->log('Failed to upload custom rule: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }
    
    /**
     * Check request against firewall rules (cloud verification)
     *
     * @since 1.0.0
     * @param string $url Request URL
     * @param array $payload Request payload/params
     * @param string $method HTTP method
     * @param string $api_key API key for authentication
     * @return array|WP_Error Response on success, WP_Error on failure
     */
    public function check_firewall_request($url, $payload, $method, $api_key) {
        $endpoint = 'firewall/check?api_key=' . urlencode($api_key);
        
        $request_data = array(
            'url' => $url,
            'payload' => $payload,
            'method' => $method,
            'headers' => array(
                'User-Agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''
            )
        );
        
        $response = $this->make_request($endpoint, 'POST', $request_data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to check firewall request: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {should_block, matched_rule, rule_category, severity, action, reason}
        return $response;
    }
    
    /**
     * Get globally blocked IPs from API
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param bool $active_only Only return active blocks
     * @return array|WP_Error Array of blocked IPs on success, WP_Error on failure
     */
    public function get_blocked_ips($api_key, $active_only = true) {
        $active_param = $active_only ? 'true' : 'false';
        $endpoint = 'firewall/blocked-ips?api_key=' . urlencode($api_key) . '&active_only=' . $active_param;
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch blocked IPs: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {blocked_ips: [...], total_count: N, active_count: N}
        return $response;
    }
    
    /**
     * Get blocked IPs from API server with pagination
     *
     * @since 1.1.9
     * @param string $api_key API key for authentication
     * @param int $page Page number (default 1)
     * @param int $limit Number of IPs per page (default 1000, max 5000)
     * @param bool $active_only Only return active blocks
     * @return array|WP_Error Array of blocked IPs on success, WP_Error on failure
     */
    public function get_blocked_ips_paginated($api_key, $page = 1, $limit = 1000, $active_only = true) {
        $params = array(
            'api_key' => $api_key,
            'active_only' => $active_only ? 'true' : 'false',
            'limit' => min($limit, 5000) // Cap at 5000
        );
        
        $endpoint = 'firewall/blocked-ips?' . http_build_query($params);
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch blocked IPs: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {blocked_ips: [...], total_count: N, active_count: N, filters_applied: {...}}
        return $response;
    }
    
    /**
     * Report blocked IP to API server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string $ip_address IP address to report
     * @param string $reason Reason for blocking
     * @param int $duration_minutes Duration in minutes
     * @return array|WP_Error Response on success, WP_Error on failure
     */
    public function report_blocked_ip($api_key, $ip_address, $reason, $duration_minutes = 60) {
        $endpoint = 'firewall/block-ip?api_key=' . urlencode($api_key);
        
        $payload = array(
            'ip_address' => $ip_address,
            'reason' => $reason,
            'duration_minutes' => $duration_minutes,
            'block_type' => $duration_minutes > 0 ? 'temporary' : 'permanent'
        );
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        if (is_wp_error($response)) {
            $this->log('Failed to report blocked IP: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }
    
    /**
     * Unblock IP on API server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string $ip_address IP address to unblock
     * @return array|WP_Error Response on success, WP_Error on failure
     */
    public function unblock_ip($api_key, $ip_address) {
        $endpoint = 'firewall/unblock-ip?api_key=' . urlencode($api_key);
        
        $payload = array(
            'ip_address' => $ip_address
        );
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        if (is_wp_error($response)) {
            $this->log('Failed to unblock IP: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }
    
    // =============================================================================
    // SCANNER ENDPOINTS
    // =============================================================================
    
    /**
     * Get malware signatures from server (LEGACY — loads all into memory)
     *
     * WARNING: For large signature databases (100K+), this method may exhaust PHP memory.
     * Use get_malware_signatures_streamed() instead for memory-safe page-by-page processing.
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string|null $current_version Current signature version (for delta updates)
     * @param int $page_size Number of signatures per page (default: 1000, max: 5000)
     * @return array|WP_Error Signatures data or error
     */
    public function get_malware_signatures($api_key, $current_version = null, $page_size = 1000) {
        $all_signatures = array();
        $page = 1;
        $has_more = true;
        $metadata = array();
        
        $this->log('Starting signature fetch with pagination (page_size: ' . $page_size . ')', 'info');
        
        while ($has_more) {
            $params = array(
                'api_key' => $api_key,
                'page' => $page,
                'page_size' => $page_size
            );
            
            if ($current_version) {
                $params['current_version'] = $current_version;
            }
            
            $endpoint = 'scanner/signatures?' . http_build_query($params);
            $response = $this->make_request($endpoint, 'GET');
            
            if (is_wp_error($response)) {
                $this->log('Failed to fetch signatures (page ' . $page . '): ' . $response->get_error_message(), 'error');
                return $response;
            }
            
            // Collect signatures from this page
            if (isset($response['signatures']) && is_array($response['signatures'])) {
                $all_signatures = array_merge($all_signatures, $response['signatures']);
                $this->log('Fetched page ' . $page . '/' . ($response['total_pages'] ?? '?') . 
                          ' (' . count($response['signatures']) . ' signatures)', 'info');
            }
            
            // Store metadata from first page
            if ($page === 1) {
                $metadata = array(
                    'version' => $response['version'] ?? null,
                    'total_count' => $response['total_count'] ?? 0,
                    'server_total' => $response['server_total'] ?? 0,  // Total signatures on server (all tiers)
                    'tier' => $response['tier'] ?? 'free',
                    'last_updated' => $response['last_updated'] ?? null,
                    'next_update_at' => $response['next_update_at'] ?? null,
                );
            }
            
            // Check if there are more pages
            $has_more = isset($response['has_more']) && $response['has_more'];
            $page++;
        }
        
        $this->log('Signature fetch complete: ' . count($all_signatures) . ' total signatures', 'info');
        
        // Return combined result in original format
        return array_merge($metadata, array(
            'signatures' => $all_signatures,
            'fetched_count' => count($all_signatures)
        ));
    }

    /**
     * Get malware signatures from server with per-page streaming callback
     *
     * Memory-safe alternative to get_malware_signatures(). Instead of accumulating all
     * signatures in memory, invokes $page_callback for each page of results so the caller
     * can write them to disk incrementally. Peak memory stays under ~5-10 MB regardless
     * of total signature count.
     *
     * @since 2.2.4
     * @param string   $api_key        API key for authentication
     * @param callable $page_callback  Called for each page: function(array $signatures, int $page_number): bool
     *                                 Return false from callback to abort fetching.
     * @param string|null $current_version Current signature version (for delta updates)
     * @param int      $page_size      Number of signatures per page (default: 1000, max: 5000)
     * @return array|WP_Error Metadata (without signatures array) or error.
     *                        Keys: version, total_count, server_total, tier, last_updated, next_update_at, fetched_count
     */
    public function get_malware_signatures_streamed($api_key, callable $page_callback, $current_version = null, $page_size = 1000) {
        $page = 1;
        $has_more = true;
        $metadata = array();
        $total_fetched = 0;

        $this->log('Starting streamed signature fetch (page_size: ' . $page_size . ')', 'info');

        while ($has_more) {
            $params = array(
                'api_key' => $api_key,
                'page'    => $page,
                'page_size' => $page_size,
            );

            if ($current_version) {
                $params['current_version'] = $current_version;
            }

            $endpoint = 'scanner/signatures?' . http_build_query($params);
            $response = $this->make_request($endpoint, 'GET');

            if (is_wp_error($response)) {
                $this->log('Failed to fetch signatures (page ' . $page . '): ' . $response->get_error_message(), 'error');
                return $response;
            }

            // Store metadata from first page
            if ($page === 1) {
                $metadata = array(
                    'version'        => $response['version'] ?? null,
                    'total_count'    => $response['total_count'] ?? 0,
                    'server_total'   => $response['server_total'] ?? 0,
                    'tier'           => $response['tier'] ?? 'free',
                    'last_updated'   => $response['last_updated'] ?? null,
                    'next_update_at' => $response['next_update_at'] ?? null,
                );
            }

            // Deliver page to callback
            $page_signatures = isset($response['signatures']) && is_array($response['signatures'])
                ? $response['signatures']
                : array();

            $page_count = count($page_signatures);
            $total_fetched += $page_count;

            $this->log('Fetched page ' . $page . '/' . ($response['total_pages'] ?? '?') .
                       ' (' . $page_count . ' signatures)', 'info');

            if ($page_count > 0) {
                $continue = $page_callback($page_signatures, $page);
                // Allow callback to abort early
                if ($continue === false) {
                    $this->log('Signature fetch aborted by callback at page ' . $page, 'warning');
                    break;
                }
            }

            // Check if there are more pages
            $has_more = isset($response['has_more']) && $response['has_more'];
            $page++;
        }

        $this->log('Streamed signature fetch complete: ' . $total_fetched . ' total signatures', 'info');

        $metadata['fetched_count'] = $total_fetched;
        return $metadata;
    }
    
    /**
     * Check signature version on server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @return array|WP_Error Version info or error
     */
    public function check_signature_version($api_key) {
        $endpoint = 'scanner/check-version?api_key=' . urlencode($api_key);
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to check signature version: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {version: "1.2.0", last_updated: "2025-01-XX", signature_count: 250}
        $this->log('Server signature version: ' . ($response['version'] ?? 'unknown'), 'info');
        return $response;
    }
    
    /**
     * Submit scan result to server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param array $scan_data Scan result data (scan_type, total_files, infected_count, files, duration)
     * @return array|WP_Error Response or error
     */
    public function submit_scan_result($api_key, $scan_data) {
        $endpoint = 'scanner/scan-result?api_key=' . urlencode($api_key);
        
        // Map WordPress UI scan types to Django schema
        // WordPress UI: Limited, Standard, High Sensitivity, Custom
        // Django scan_type: full, quick, custom, targeted
        // Django scan_mode: standard, deep, quick
        $scan_type_map = array(
            // WordPress legacy types
            'malware' => array('type' => 'full', 'mode' => 'standard'),
            'full' => array('type' => 'full', 'mode' => 'standard'),
            
            // WordPress UI options (from image)
            'limited' => array('type' => 'quick', 'mode' => 'quick'),      // Limited Scan -> quick scan, quick mode
            'standard' => array('type' => 'full', 'mode' => 'standard'),    // Standard Scan -> full scan, standard mode
            'high' => array('type' => 'full', 'mode' => 'deep'),            // High Sensitivity -> full scan, deep mode
            'custom' => array('type' => 'custom', 'mode' => 'standard'),    // Custom Scan -> custom scan, standard mode
            
            // Django direct values (for compatibility)
            'quick' => array('type' => 'quick', 'mode' => 'quick'),
            'targeted' => array('type' => 'targeted', 'mode' => 'standard')
        );
        
        $wp_scan_type = isset($scan_data['scan_type']) ? strtolower($scan_data['scan_type']) : 'standard';
        $scan_config = isset($scan_type_map[$wp_scan_type]) ? $scan_type_map[$wp_scan_type] : array('type' => 'full', 'mode' => 'standard');
        
        // Override scan_mode if explicitly provided
        if (isset($scan_data['scan_mode'])) {
            $scan_config['mode'] = $scan_data['scan_mode'];
        }
        
        // Build payload matching Django schema (ScanResultSubmitRequest)
        $payload = array(
            // Required fields
            'scan_type' => $scan_config['type'],
            'scan_mode' => $scan_config['mode'],
            'started_at' => isset($scan_data['started_at']) ? $scan_data['started_at'] : gmdate('Y-m-d\TH:i:s\Z'),
            'completed_at' => isset($scan_data['completed_at']) ? $scan_data['completed_at'] : gmdate('Y-m-d\TH:i:s\Z'),
            'duration_seconds' => isset($scan_data['duration']) ? intval($scan_data['duration']) : 0,
            
            // Statistics (matching Django field names)
            'files_scanned' => isset($scan_data['total_files']) ? intval($scan_data['total_files']) : 0,
            'files_skipped' => isset($scan_data['files_skipped']) ? intval($scan_data['files_skipped']) : 0,
            'threats_found' => isset($scan_data['infected_count']) ? intval($scan_data['infected_count']) : 0,
            'threats_cleaned' => isset($scan_data['threats_cleaned']) ? intval($scan_data['threats_cleaned']) : 0,
            'threats_quarantined' => isset($scan_data['threats_quarantined']) ? intval($scan_data['threats_quarantined']) : 0,
            
            // Infected files array (matching InfectedFileSchema)
            'infected_files' => isset($scan_data['files']) ? $scan_data['files'] : array(),
            
            // Optional performance metrics
            'avg_file_scan_time_ms' => isset($scan_data['avg_file_scan_time_ms']) ? floatval($scan_data['avg_file_scan_time_ms']) : null,
            'total_bytes_scanned' => isset($scan_data['total_bytes_scanned']) ? intval($scan_data['total_bytes_scanned']) : null,
            
            // Optional errors
            'scan_errors' => isset($scan_data['scan_errors']) ? $scan_data['scan_errors'] : null,
        );
        
        // Log payload for debugging (only in debug mode)
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $this->log('Scan submission payload: ' . wp_json_encode($payload), 'debug');
        }
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        if (is_wp_error($response)) {
            $this->log('Failed to submit scan result: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('Scan result submitted successfully', 'info');
        return $response;
    }
    
    /**
     * Get network-wide attack timeline statistics
     *
     * @since 2.0.0
     * @param string $api_key API key for authentication
     * @param string $period Time period ('24h' or '30d')
     * @return array|WP_Error Timeline data or error
     */
    public function get_attack_timeline($api_key, $period = '24h') {
        // Build data array - api_key will be added to query string, period as separate param
        $data = array(
            'api_key' => $api_key,
            'period' => $period
        );
        
        // Pass clean endpoint name for mapper resolution
        $response = $this->make_request('firewall/attack-timeline', 'GET', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to get attack timeline: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('Attack timeline retrieved successfully', 'info');
        return $response;
    }
    
    /**
     * Get scan history from server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $page Page number (default 1)
     * @param int $limit Results per page (default 20)
     * @return array|WP_Error Scan history or error
     */
    public function get_scan_history($api_key, $page = 1, $limit = 20) {
        $params = array(
            'api_key' => $api_key,
            'page' => max(1, intval($page)),
            'limit' => min(100, max(1, intval($limit))) // Cap at 100
        );
        
        $endpoint = 'scanner/scan-history?' . http_build_query($params);
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch scan history: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {scans: [...], total: N, page: 1, limit: 20}
        $this->log('Fetched scan history - total: ' . (isset($response['total']) ? $response['total'] : 0), 'info');
        return $response;
    }
    
    /**
     * Get scanner statistics from server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @return array|WP_Error Statistics or error
     */
    public function get_scanner_stats($api_key) {
        $endpoint = 'scanner/stats?api_key=' . urlencode($api_key);
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch scanner stats: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    /**
     * Get YARA batch scan history from server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $page Page number (default 1)
     * @param int $page_size Results per page (default 20)
     * @return array|WP_Error Batch scan history or error
     */
    public function get_yara_batch_history($api_key, $page = 1, $page_size = 20) {
        $params = array(
            'page' => max(1, intval($page)),
            'page_size' => min(100, max(1, intval($page_size)))
        );
        
        $endpoint = 'yara/batch-scan-history?' . http_build_query($params);
        $headers = array(
            'Authorization' => 'Bearer ' . $api_key
        );
        
        $response = $this->make_request($endpoint, 'GET', array(), $headers);
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch YARA batch history: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('Fetched YARA batch history - sessions: ' . (isset($response['total_count']) ? $response['total_count'] : 0), 'info');
        return $response;
    }

    /**
     * Get detailed results for a specific batch scan
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string $batch_id Batch ID
     * @return array|WP_Error Batch results or error
     */
    public function get_yara_batch_results($api_key, $batch_id) {
        $endpoint = 'yara/batch-scan/' . urlencode($batch_id) . '/results';
        $headers = array(
            'Authorization' => 'Bearer ' . $api_key
        );
        
        $response = $this->make_request($endpoint, 'GET', array(), $headers);
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch batch results: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }
    
    /**
     * YARA Deep Scan - Server-side advanced malware detection
     * 
     * Premium feature: Send suspicious files to server for YARA rule scanning.
     * Uses 1000+ enterprise-grade YARA rules for advanced threat detection.
     *
     * @since 1.1.0
     * @param string $api_key API key for authentication (must be premium)
     * @param string $file_path Absolute path to file to scan
     * @param string $scan_reason Reason for scan (suspicious, user_requested, scheduled, etc.)
     * @return array|WP_Error Scan result with matched rules or error
     */
    public function yara_deep_scan($api_key, $file_path, $scan_reason = 'suspicious') {
        // Validate file exists and is readable
        if (!file_exists($file_path) || !is_readable($file_path)) {
            return new WP_Error('file_not_found', 'File not found or not readable: ' . $file_path);
        }
        
        // Check file size (max 10MB for YARA scan)
        $file_size = filesize($file_path);
        $max_size = 10 * 1024 * 1024; // 10MB
        
        if ($file_size > $max_size) {
            return new WP_Error('file_too_large', 'File exceeds maximum size for YARA scan (10MB): ' . size_format($file_size));
        }
        
        if ($file_size === 0) {
            return new WP_Error('empty_file', 'Cannot scan empty file');
        }
        
        // Read file content and base64 encode
        $file_content = file_get_contents($file_path);
        if ($file_content === false) {
            return new WP_Error('read_error', 'Failed to read file content');
        }
        
        $file_content_base64 = base64_encode($file_content);
        
        // Calculate file hash (SHA256)
        $file_hash = hash('sha256', $file_content);
        
        // Get filename without full path
        $file_name = basename($file_path);
        
        // Get current WordPress user ID
        $user_id = get_current_user_id();
        
        // Build payload
        $payload = array(
            'file_name' => $file_name,
            'file_content' => $file_content_base64,
            'file_hash' => $file_hash,
            'scan_reason' => $scan_reason,
            'wp_user_id' => intval($user_id)
        );
        
        $endpoint = 'yara/deep-scan?api_key=' . urlencode($api_key);
        
        // Increase timeout for YARA scan (can take 10-30 seconds for large files)
        $original_timeout = $this->timeout;
        $this->timeout = 60;
        
        $this->log('Initiating YARA deep scan for: ' . $file_name . ' (' . size_format($file_size) . ')', 'info');
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        // Restore original timeout
        $this->timeout = $original_timeout;
        
        if (is_wp_error($response)) {
            // Check if it's a premium-only error
            if ($response->get_error_code() === 'http_403') {
                return new WP_Error('premium_only', 'YARA Deep Scan is a premium-only feature. Please upgrade your license.');
            }
            
            $this->log('YARA deep scan failed: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response format: {success, threats_found, matches[], file_hash, recommendation, scan_time}
        $threats_found = isset($response['threats_found']) ? $response['threats_found'] : 0;
        $this->log('YARA deep scan completed: ' . $threats_found . ' threats detected', 'info');
        
        return $response;
    }
    
    /**
     * Submit batch YARA scan
     * 
     * @param string $api_key API key for authentication
     * @param array $files_metadata Array of file metadata (file_name, file_path, file_hash, file_size)
     * @param int|null $batch_number Batch number in plugin session (e.g., 1 of 26)
     * @param int|null $total_batches Total batches in plugin session
     * @return array|WP_Error Batch scan response with batch_id or error
     */
    public function yara_batch_scan($api_key, $files_metadata, $batch_number = null, $total_batches = null) {
        $this->log('Submitting YARA batch scan with ' . count($files_metadata) . ' files (batch ' . ($batch_number ?? '?') . '/' . ($total_batches ?? '?') . ')', 'info');
        
        // Get current WordPress user ID
        $user_id = get_current_user_id();
        
        $payload = array(
            'files' => $files_metadata,
            'wp_user_id' => intval($user_id)
        );
        
        // Add batch metadata if provided
        if ($batch_number !== null) {
            $payload['batch_number'] = $batch_number;
        }
        if ($total_batches !== null) {
            $payload['total_batches'] = $total_batches;
        }
        
        // Add API key to payload (will be moved to query string by make_request)
        $payload['api_key'] = $api_key;
        
        $endpoint = 'yara/batch-scan';
        
        // Increase timeout for batch submission
        $original_timeout = $this->timeout;
        $this->timeout = 60;
        
        $response = $this->make_request($endpoint, 'POST', $payload);
        
        $this->timeout = $original_timeout;
        
        if (is_wp_error($response)) {
            $this->log('YARA batch scan submission failed: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('YARA batch scan submitted successfully, batch ID: ' . ($response['batch_id'] ?? 'unknown'), 'info');
        
        return $response;
    }
    
    /**
     * Get batch scan status
     * 
     * @param string $api_key API key for authentication
     * @param string $batch_id UUID of the batch scan
     * @return array|WP_Error Status information or error
     */
    public function yara_batch_status($api_key, $batch_id) {
        $endpoint = 'yara/batch/' . urlencode($batch_id) . '/status?api_key=' . urlencode($api_key);
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            return $response;
        }
        
        return $response;
    }
    
    /**
     * Get batch scan results
     * 
     * @param string $api_key API key for authentication
     * @param string $batch_id UUID of the batch scan
     * @return array|WP_Error Complete batch results or error
     */
    public function yara_batch_results($api_key, $batch_id) {
        $endpoint = 'yara/batch/' . urlencode($batch_id) . '?api_key=' . urlencode($api_key);
        
        $this->log('Fetching YARA batch results for batch: ' . $batch_id, 'info');
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch batch results: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('YARA batch results fetched successfully', 'info');
        
        return $response;
    }

    /**
     * Cancel a YARA batch scan
     *
     * @param string $api_key API key for authentication
     * @param string $batch_id Batch ID to cancel
     * @return array|WP_Error Response or error
     */
    public function cancel_yara_batch($api_key, $batch_id) {
        $endpoint = 'yara/batch/' . urlencode($batch_id) . '?api_key=' . urlencode($api_key);
        
        $this->log('Cancelling YARA batch: ' . $batch_id, 'info');
        
        $response = $this->make_request($endpoint, 'DELETE');
        
        if (is_wp_error($response)) {
            $this->log('Failed to cancel batch: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('YARA batch cancelled successfully', 'info');
        
        return $response;
    }

    /**
     * Get YARA scan statistics from server
     *
     * @param string $api_key API key for authentication
     * @return array|WP_Error Response containing scan stats (total_scans, threats_found, clean_files, etc.)
     */
    public function get_yara_stats($api_key) {
        $endpoint = 'yara/stats';
        $headers = array(
            'Authorization' => 'Bearer ' . $api_key
        );
        
        $this->log('Fetching YARA stats from server', 'info');
        
        $response = $this->make_request($endpoint, 'GET', array(), $headers);
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch YARA stats: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('YARA stats fetched successfully - Active rules: ' . (isset($response['active_rules']) ? $response['active_rules'] : 0), 'info');
        
        return $response;
    }

    // ============================================================================
    // TWO-FACTOR AUTHENTICATION (2FA) METHODS
    // ============================================================================

    /**
     * Setup 2FA for a user (generates QR code and secret)
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @return array|WP_Error Response with qr_code, secret, backup_codes or error
     */
    public function setup_2fa($api_key, $user_id) {
        $endpoint = 'login/setup-2fa';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id),
            'method' => 'totp'
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to setup 2FA for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('2FA setup initiated for user ' . $user_id, 'info');
        return $response;
    }

    /**
     * Verify 2FA setup with TOTP code
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @param string $code TOTP code from authenticator app
     * @return array|WP_Error Response with success status or error
     */
    public function verify_2fa_setup($api_key, $user_id, $code) {
        $endpoint = 'login/verify-2fa-setup';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id),
            'code' => sanitize_text_field($code)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to verify 2FA setup for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('2FA setup verified for user ' . $user_id, 'info');
        return $response;
    }

    /**
     * Enable 2FA for a user (after successful setup verification)
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @return array|WP_Error Response with success status or error
     */
    public function enable_2fa($api_key, $user_id) {
        // Note: 2FA is automatically enabled during verify-2fa-setup
        // This method is kept for backward compatibility
        $this->log('2FA already enabled during verification for user ' . $user_id, 'info');
        return array('success' => true, 'message' => '2FA already enabled');
    }

    /**
     * Disable 2FA for a user
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @return array|WP_Error Response with success status or error
     */
    public function disable_2fa($api_key, $user_id) {
        $endpoint = 'login/disable-2fa';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to disable 2FA for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('2FA disabled for user ' . $user_id, 'info');
        return $response;
    }

    /**
     * Check 2FA status for a user
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @return array|WP_Error Response with is_enabled status or error
     */
    public function get_2fa_status($api_key, $user_id) {
        $endpoint = 'login/2fa-status';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to get 2FA status for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    /**
     * Verify 2FA code during login
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @param string $code TOTP code or backup code
     * @return array|WP_Error Response with valid status or error
     */
    public function verify_2fa_code($api_key, $user_id, $code) {
        $endpoint = 'login/verify-2fa';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id),
            'code' => sanitize_text_field($code)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to verify 2FA code for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    /**
     * Verify backup code during login
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @param string $backup_code Backup code
     * @return array|WP_Error Response with valid status or error
     */
    public function verify_backup_code($api_key, $user_id, $backup_code) {
        $endpoint = 'login/verify-backup-code';
        
        // CRITICAL: Normalize backup code - remove dash before hashing on server
        // Server stores codes with dash (1234-5678) but user might enter without (12345678)
        // We need to send in consistent format for hash matching
        $backup_code_normalized = str_replace('-', '', $backup_code);
        
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id),
            'code' => sanitize_text_field($backup_code_normalized)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to verify backup code for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    /**
     * Regenerate backup codes for a user
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @return array|WP_Error Response with new backup_codes or error
     */
    public function regenerate_backup_codes($api_key, $user_id) {
        $endpoint = 'login/regenerate-backup-codes';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to regenerate backup codes for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        $this->log('Backup codes regenerated for user ' . $user_id, 'info');
        return $response;
    }

    /**
     * Get login attempts for a user
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param int $user_id WordPress user ID
     * @param int $limit Number of attempts to retrieve (default 10)
     * @return array|WP_Error Response with login attempts or error
     */
    public function get_login_attempts($api_key, $user_id, $limit = 10) {
        $endpoint = 'login/login-attempts';
        $data = array(
            'api_key' => $api_key,
            'wp_user_id' => intval($user_id),
            'limit' => intval($limit)
        );
        
        $response = $this->make_request($endpoint, 'POST', $data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to get login attempts for user ' . $user_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    // ============================================================================
    // LOGGING AND ANALYTICS METHODS
    // ============================================================================

    /**
     * Log a single security event
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param array  $event_data Event data
     * @return array|WP_Error API response or error
     */
    public function log_event($api_key, $event_data) {
        $endpoint = 'logging/event';
        $data = array_merge(
            array('api_key' => $api_key),
            $event_data
        );

        $response = $this->make_request($endpoint, 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to log event: ' . $response->get_error_message(), 'error');
            return $response;
        }

        $this->log('Event logged: ' . $event_data['event_type'], 'info');
        return $response;
    }

    /**
     * Log multiple events in batch
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param array  $events Array of event data
     * @return array|WP_Error API response or error
     */
    public function log_event_batch($api_key, $events) {
        $endpoint = 'logging/event/batch';
        $data = array(
            'api_key' => $api_key,
            'events' => $events
        );

        $response = $this->make_request($endpoint, 'POST', $data);

        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();
            VMPFence_Secure_Logger::log('error', 'API: Failed to log event batch', ['error' => $error_message]);
            $this->log('Failed to log event batch: ' . $error_message, 'error');
            return $response;
        }

        $this->log('Batch logged: ' . count($events) . ' events', 'info');
        return $response;
    }

    /**
     * Record a performance metric
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param array  $metric_data Metric data
     * @return array|WP_Error API response or error
     */
    public function record_metric($api_key, $metric_data) {
        $endpoint = 'logging/metrics';
        $data = array_merge(
            array('api_key' => $api_key),
            $metric_data
        );

        $response = $this->make_request($endpoint, 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to record metric: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Get dashboard analytics
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param string $period Period: 7d, 30d, 90d
     * @return array|WP_Error Analytics data or error
     */
    public function get_dashboard_analytics($api_key, $period = '30d') {
        $endpoint = 'logging/analytics';
        $data = array(
            'api_key' => $api_key,
            'period' => $period
        );

        $response = $this->make_request($endpoint, 'GET', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get analytics: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Get events with optional filtering
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param array  $params Filter parameters (page, page_size, severity, event_type, start_date, end_date)
     * @return array|WP_Error Events list or error
     */
    public function get_events($api_key, $params = array()) {
        $endpoint = 'logging/events';
        $data = array_merge(
            array('api_key' => $api_key),
            $params
        );

        $response = $this->make_request($endpoint, 'GET', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get events: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Get event timeline data
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param int    $hours Number of hours
     * @return array|WP_Error Timeline data or error
     */
    public function get_event_timeline($api_key, $hours = 24) {
        $endpoint = 'logging/timeline';
        $data = array(
            'api_key' => $api_key,
            'hours' => intval($hours)
        );

        $response = $this->make_request($endpoint, 'GET', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get event timeline: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Get daily statistics
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param int    $days Number of days
     * @return array|WP_Error Statistics or error
     */
    public function get_daily_stats($api_key, $days = 30) {
        $endpoint = 'logging/stats/daily';
        $data = array(
            'api_key' => $api_key,
            'days' => intval($days)
        );

        $response = $this->make_request($endpoint, 'GET', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get daily stats: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Export logs
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @param string $format Export format: json, csv
     * @param array  $filters Filter parameters
     * @return array|WP_Error Export data or error
     */
    public function export_logs($api_key, $format = 'json', $filters = array()) {
        $endpoint = 'logging/export';
        $data = array_merge(
            array(
                'api_key' => $api_key,
                'format' => $format
            ),
            $filters
        );

        $response = $this->make_request($endpoint, 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to export logs: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    /**
     * Get retention policy
     *
     * @since 1.0.0
     * @param string $api_key API key
     * @return array|WP_Error Retention policy or error
     */
    public function get_retention_policy($api_key) {
        $endpoint = 'logging/retention-policy';
        $data = array('api_key' => $api_key);

        $response = $this->make_request($endpoint, 'GET', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to get retention policy: ' . $response->get_error_message(), 'error');
            return $response;
        }

        return $response;
    }

    // ============================================================================
    // CVE VULNERABILITY ENDPOINTS
    // ============================================================================

    /**
     * Get CVE vulnerabilities from server
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @return array|WP_Error CVE data or error
     */
    public function get_cve_vulnerabilities($api_key) {
        // Request all CVEs with high limit (API has pagination, default is 50)
        $endpoint = 'vulnerability/cves?api_key=' . urlencode($api_key) . '&limit=10000';
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to fetch CVE vulnerabilities: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Response: {cves: [...], total: 2001, limit: 50, offset: 0}
        $total = isset($response['cves']) ? count($response['cves']) : (isset($response['total']) ? $response['total'] : 0);
        $this->log('Fetched ' . $total . ' CVE vulnerabilities from server (Total available: ' . (isset($response['total']) ? $response['total'] : 'unknown') . ')', 'info');
        return $response;
    }

    /**
     * Check site for CVE vulnerabilities
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param array $site_data Site component data (wordpress_version, plugins, themes)
     * @return array|WP_Error Vulnerability scan results or error
     */
    public function check_site_vulnerabilities($api_key, $site_data) {
        $endpoint = 'vulnerability/check-site?api_key=' . urlencode($api_key);
        
        $this->log('POST data being sent: ' . wp_json_encode($site_data), 'debug');
        
        $response = $this->make_request($endpoint, 'POST', $site_data);
        
        if (is_wp_error($response)) {
            $this->log('Failed to check site vulnerabilities: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Log full response for debugging
        $this->log('Full API response: ' . wp_json_encode($response), 'debug');
        
        // Response: {vulnerabilities: [...], summary: {total, critical, high, medium, low}}
        $total = isset($response['summary']['total']) ? $response['summary']['total'] : 0;
        $this->log('Site vulnerability check completed: ' . $total . ' vulnerabilities found', 'info');
        return $response;
    }

    /**
     * Get CVE details by ID
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string $cve_id CVE identifier (e.g., CVE-2023-12345)
     * @return array|WP_Error CVE details or error
     */
    public function get_cve_details($api_key, $cve_id) {
        $endpoint = 'vulnerability/cve/' . urlencode($cve_id) . '?api_key=' . urlencode($api_key);
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to get CVE details for ' . $cve_id . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    /**
     * Search CVEs by component (plugin/theme slug)
     *
     * @since 1.0.0
     * @param string $api_key API key for authentication
     * @param string $component_slug Component slug (e.g., woocommerce, contact-form-7)
     * @return array|WP_Error CVE list or error
     */
    public function search_cves_by_component($api_key, $component_slug) {
        $endpoint = 'vulnerability/component/' . urlencode($component_slug) . '?api_key=' . urlencode($api_key);
        
        $response = $this->make_request($endpoint, 'GET');
        
        if (is_wp_error($response)) {
            $this->log('Failed to search CVEs for component ' . $component_slug . ': ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        return $response;
    }

    // ============================================================================
    // SYNC METHODS (LOCAL-FIRST ARCHITECTURE)
    // ============================================================================

    /**
     * Sync events to server (24-hour background sync)
     *
     * @since 2.0.0
     * @param string $api_key API key for authentication
     * @param array  $events Array of events to sync
     * @return array|WP_Error Sync response or error
     */
    public function sync_events($api_key, $events) {
        $data = array(
            'site_id' => $api_key,
            'events' => $events
        );

        $response = $this->make_request('logging/sync-events?api_key=' . urlencode($api_key), 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to sync events: ' . $response->get_error_message(), 'error');
            return $response;
        }

        $this->log(sprintf('Successfully synced %d events', count($events)), 'info');
        return $response;
    }

    /**
     * Sync analytics summary to server
     *
     * @since 2.0.0
     * @param string $api_key API key for authentication
     * @param array  $analytics Array of analytics data
     * @return array|WP_Error Sync response or error
     */
    public function sync_analytics($api_key, $analytics) {
        $data = array(
            'site_id' => $api_key,
            'analytics' => $analytics
        );

        $response = $this->make_request('logging/sync-analytics?api_key=' . urlencode($api_key), 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to sync analytics: ' . $response->get_error_message(), 'error');
            return $response;
        }

        $this->log(sprintf('Successfully synced %d days of analytics', count($analytics)), 'info');
        return $response;
    }

    // ============================================================================
    // SIEM BATCH SYNC METHODS (DB v2.3.0) (PLUGIN v2.2.4)
    // ============================================================================

    /**
     * Identify site with server (public endpoint, no auth required)
     *
     * @since 2.2.4
     * @param array $site_data Site identification payload
     * @return array|WP_Error Response with site_uuid
     */
    public function identify_site($site_data) {
        $response = $this->make_request('public/sites/identify', 'POST', $site_data);
        if (is_wp_error($response)) {
            $this->log('Site identification failed: ' . $response->get_error_message(), 'error');
        }
        return $response;
    }

    /**
     * Batch sync audit logs to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Audit log records
     * @return array|WP_Error
     */
    public function sync_audit_logs_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('logging/audit-logs/batch', $api_key, $site_uuid, $records, 'logs');
    }

    /**
     * Batch sync file changes to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records File change records
     * @return array|WP_Error
     */
    public function sync_file_changes_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('scanner/file-changes/batch', $api_key, $site_uuid, $records, 'changes');
    }

    /**
     * Batch sync WAF failures to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records WAF failure records
     * @return array|WP_Error
     */
    public function sync_waf_failures_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('firewall/waf-failures/batch', $api_key, $site_uuid, $records, 'failures');
    }

    /**
     * Batch sync country block logs to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Country block records
     * @return array|WP_Error
     */
    public function sync_country_blocks_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('firewall/country-blocks/batch', $api_key, $site_uuid, $records, 'blocks');
    }

    /**
     * Batch sync bot/crawler data to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Bot crawler records
     * @return array|WP_Error
     */
    public function sync_bot_crawlers_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('firewall/bot-crawlers/batch', $api_key, $site_uuid, $records, 'crawlers');
    }

    /**
     * Batch sync custom WAF patterns to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Custom pattern records
     * @return array|WP_Error
     */
    public function sync_custom_patterns_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('firewall/custom-patterns/batch', $api_key, $site_uuid, $records, 'patterns');
    }

    /**
     * Batch sync temporary blocks to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Temporary block records
     * @return array|WP_Error
     */
    public function sync_temporary_blocks_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('firewall/temporary-blocks/batch', $api_key, $site_uuid, $records, 'blocks');
    }

    /**
     * Batch sync WHOIS cache data to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records WHOIS cache records
     * @return array|WP_Error
     */
    public function sync_whois_cache_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('threat-intel/whois-cache/batch', $api_key, $site_uuid, $records, 'entries');
    }

    /**
     * Batch sync lockout state to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records Lockout state records
     * @return array|WP_Error
     */
    public function sync_lockout_state_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('login/lockout-state/batch', $api_key, $site_uuid, $records, 'lockouts');
    }

    /**
     * Batch sync file modifications to backend SIEM
     *
     * @since 2.2.4
     * @param string $api_key API key (used as Bearer token)
     * @param string $site_uuid Site UUID
     * @param array  $records File modification records
     * @return array|WP_Error
     */
    public function sync_file_modifications_batch($api_key, $site_uuid, $records) {
        return $this->_siem_batch_sync('logging/file-modifications/batch', $api_key, $site_uuid, $records, 'modifications');
    }

    /**
     * Internal: Generic SIEM batch sync
     * Uses PluginAPIKeyAuth (Bearer token = existing API key)
     *
     * NOTE: $data_key MUST match the server-side payload key.
     * Each server endpoint reads a specific key (e.g., 'logs', 'failures', 'crawlers'),
     * NOT a generic 'records' key. Using the wrong key results in silent 0-created syncs.
     *
     * Server payload keys (verified Feb 16, 2026):
     *   audit-logs/batch       → 'logs'
     *   file-changes/batch     → 'changes'
     *   waf-failures/batch     → 'failures'
     *   country-blocks/batch   → 'blocks'
     *   bot-crawlers/batch     → 'crawlers'
     *   custom-patterns/batch  → 'patterns'
     *   temporary-blocks/batch → 'blocks'
     *   whois-cache/batch      → 'entries'
     *   lockout-state/batch    → 'lockouts'
     *   file-modifications/batch → 'modifications'
     *
     * @since 2.2.4
     * @param string $endpoint  API endpoint path (without /api/v1/ prefix)
     * @param string $api_key   API key sent as Bearer token
     * @param string $site_uuid Site UUID for server-side association
     * @param array  $records   Transformed records to sync
     * @param string $data_key  Server-expected payload key for the records array
     * @return array|WP_Error
     */
    private function _siem_batch_sync($endpoint, $api_key, $site_uuid, $records, $data_key) {
        $data = array(
            'site_uuid' => $site_uuid,
            $data_key   => $records,
        );

        // Send API key as Bearer token in Authorization header
        // This is what the server's PluginAPIKeyAuth expects
        $headers = array('Authorization' => 'Bearer ' . $api_key);

        $response = $this->make_request($endpoint, 'POST', $data, $headers);

        if (is_wp_error($response)) {
            $this->log('SIEM batch sync failed (' . $endpoint . '): ' . $response->get_error_message(), 'error');
        }

        return $response;
    }

    // ============================================================================
    // SIEM SCAN + SETTINGS + COMMANDS (DB v2.3.0) (PLUGIN v2.2.4)
    // ============================================================================

    /**
     * Push scan progress to server (called every 30s during scan).
     *
     * @since 2.2.4
     * @param string $api_key  API key for authentication
     * @param string $site_uuid Site UUID
     * @param array  $data     Scan progress payload
     * @return array|WP_Error Server response or error
     */
    public function push_scan_progress($api_key, $site_uuid, $data) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/scan/progress', 'POST', $data, $headers
        );
    }

    /**
     * Push scan results (issues) to server (called after scan completes).
     *
     * @since 2.2.4
     * @param string $api_key  API key for authentication
     * @param string $site_uuid Site UUID
     * @param array  $data     Scan results payload
     * @return array|WP_Error Server response or error
     */
    public function push_scan_results($api_key, $site_uuid, $data) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/scan/results', 'POST', $data, $headers
        );
    }

    /**
     * Push settings snapshot to server (called every 5-min sync).
     *
     * @since 2.2.4
     * @param string $api_key  API key for authentication
     * @param string $site_uuid Site UUID
     * @param array  $data     Settings payload
     * @return array|WP_Error Server response or error
     */
    public function push_settings_sync($api_key, $site_uuid, $data) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/settings/sync', 'POST', $data, $headers
        );
    }

    /**
     * Poll for pending commands from SIEM portal.
     *
     * @since 2.2.4
     * @param string $api_key  API key for authentication
     * @param string $site_uuid Site UUID
     * @return array|WP_Error Array of pending commands or error
     */
    public function get_pending_commands($api_key, $site_uuid) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/pending-commands', 'GET', array(), $headers
        );
    }

    /**
     * Acknowledge command receipt.
     *
     * @since 2.2.4
     * @param string $api_key    API key for authentication
     * @param string $site_uuid  Site UUID
     * @param int    $command_id Command ID
     * @return array|WP_Error Server response or error
     */
    public function acknowledge_command($api_key, $site_uuid, $command_id) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/commands/' . $command_id . '/acknowledge',
            'POST', array('plugin_version' => VMPFENCE_VERSION), $headers
        );
    }

    /**
     * Report command execution result.
     *
     * @since 2.2.4
     * @param string      $api_key    API key for authentication
     * @param string      $site_uuid  Site UUID
     * @param int         $command_id Command ID
     * @param string      $status     Execution status (completed|failed)
     * @param string|null $error      Error message if failed
     * @param array|null  $result     Result data if completed
     * @return array|WP_Error Server response or error
     */
    public function complete_command($api_key, $site_uuid, $command_id, $status, $error = null, $result = null) {
        $headers = array('Authorization' => 'Bearer ' . $api_key);
        return $this->make_request(
            'sites/' . $site_uuid . '/commands/' . $command_id . '/complete',
            'POST', array(
                'success'        => ($status === 'completed'),
                'error_message'  => $error,
                'result'         => $result,
                'plugin_version' => VMPFENCE_VERSION,
            ), $headers
        );
    }

    // ============================================================================
    // CONFIGURATION IMPORT/EXPORT METHODS
    // ============================================================================

    /**
     * Export configuration to cloud
     *
     * @since 2.2.0
     * @param string $api_key API key for authentication
     * @param array  $config_data Configuration data to export
     * @param string $site_url Source site URL
     * @return array|WP_Error Export response with token or error
     */
    public function export_config_to_cloud($api_key, $config_data, $site_url = '') {
        if (empty($site_url)) {
            $site_url = get_site_url();
        }

        $data = array(
            'api_key' => $api_key,
            'config_data' => $config_data,
            'site_url' => $site_url
        );

        $response = $this->make_request('config/export', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to export configuration: ' . $response->get_error_message(), 'error');
            return $response;
        }

        if (isset($response['success']) && $response['success']) {
            $this->log('Configuration exported successfully: ' . ($response['token'] ?? 'no token'), 'info');
        }

        return $response;
    }

    /**
     * Import configuration from cloud
     *
     * @since 2.2.0
     * @param string $token Import token from export
     * @return array|WP_Error Import response with configuration data or error
     */
    public function import_config_from_cloud($token) {
        $data = array(
            'token' => $token
        );

        $response = $this->make_request('config/import', 'POST', $data);

        if (is_wp_error($response)) {
            $this->log('Failed to import configuration: ' . $response->get_error_message(), 'error');
            return $response;
        }

        if (isset($response['success']) && $response['success']) {
            $this->log('Configuration imported successfully from: ' . ($response['source_site'] ?? 'unknown site'), 'info');
        }

        return $response;
    }
}
