<?php
namespace UltraguardPro\support;
use UltraguardPro\core\CacheManager;

if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
 * ============================================================================
 * DEMO & REFERENCE EXAMPLES
 * ============================================================================
 *
 * This section provides practical examples of how to use the LicenseInfo class.
 * These examples demonstrate the fluent, chainable interface in action.
 *
 * @category Examples
 * @package  UltraguardPro\core
 * @author   UltraGuard Pro Team
 * @version  5.2.0
 *
 * ============================================================================
 * BASIC USAGE EXAMPLES
 * ============================================================================
 *
 * // 1. SIMPLE LICENSE VALIDATION
 * // -------------------------------------------------------------------------
 * $isValid = LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withEmail('user@example.com')
 *     ->validate()
 *     ->succeeded();
 *
 *
 * // 2. CHECK IF PRO IS ACTIVE
 * // -------------------------------------------------------------------------
 * if (LicenseInfo::instance()->isPro()) {
 *     // Pro features available
 * }
 *
 *
 * // 3. ACTIVATE LICENSE WITH CHAINING
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withEmail('user@example.com')
 *     ->withData(['site_type' => 'ecommerce'])
 *     ->onSuccess(function($result) {
 *         echo "License activated! Expires: " . $result['expires'];
 *     })
 *     ->onError(function($error) {
 *         echo "Activation failed: " . $error->get_error_message();
 *     })
 *     ->activate()
 *     ->send();
 *
 *
 * ============================================================================
 * IP REPUTATION EXAMPLES
 * ============================================================================
 *
 * // 4. CHECK SINGLE IP
 * // -------------------------------------------------------------------------
 * $result = LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->checkIp('192.168.1.100')
 *     ->getResult();
 *
 * if ($result['is_blacklisted']) {
 *     // Block this IP
 * }
 *
 *
 * // 5. BULK IP CHECK WITH CUSTOM TIMEOUT
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withTimeout(30)
 *     ->force(true) // Bypass cache
 *     ->checkIps(['192.168.1.1', '10.0.0.1', '8.8.8.8'])
 *     ->then(function($instance) {
 *         $results = $instance->getResult();
 *         foreach ($results['results'] as $ip => $data) {
 *             echo "$ip: " . ($data['is_blacklisted'] ? 'BLOCKED' : 'CLEAN');
 *         }
 *     })
 *     ->send();
 *
 *
 * // 6. REPORT HACK ATTEMPT WITH METADATA
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withMeta(['severity' => 'high', 'attack_type' => 'sql_injection'])
 *     ->reportHack([
 *         'ip' => '203.0.113.42',
 *         'uri' => '/wp-admin/admin-ajax.php',
 *         'payload' => base64_encode($malicious_input)
 *     ]);
 *
 *
 * ============================================================================
 * SIGNATURE & FIREWALL EXAMPLES
 * ============================================================================
 *
 * // 7. SYNC SIGNATURES ASYNC WITH PROGRESS
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->async(true)
 *     ->onSuccess(function($result) {
 *         if ($result['updated']) {
 *             echo "Updated {$result['count']} signatures to v{$result['version']}";
 *         }
 *     })
 *     ->syncSignatures()
 *     ->send();
 *
 *
 * // 8. GET FIREWALL RULES WITH CACHE CONTROL
 * // -------------------------------------------------------------------------
 * $rules = LicenseInfo::make()
 *     ->noCache() // Force fresh data
 *     ->getFirewallRules();
 *
 *
 * // 9. CONDITIONAL SYNC BASED ON PLAN
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->when(LicenseInfo::instance()->isPro(), function($license) {
 *         $license->syncAll(); // Pro gets full sync
 *     })
 *     ->otherwise(function($license) {
 *         $license->syncSignatures(); // Free only gets signatures after delay
 *     });
 *
 *
 * ============================================================================
 * BATCH OPERATIONS EXAMPLES
 * ============================================================================
 *
 * // 10. BATCH MULTIPLE OPERATIONS
 * // -------------------------------------------------------------------------
 * $batch = LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->batch(true)
 *     ->syncAll() // Queue multiple operations
 *     ->executeBatch();
 *
 * $results = $batch->getBatchResults();
 *
 *
 * // 11. CHAINED CALLBACKS AND MAPPING
 * // -------------------------------------------------------------------------
 * $blacklistedCount = LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->checkIps($ip_list)
 *     ->map(function($result) {
 *         return array_count_values(
 *             array_column($result['results'], 'is_blacklisted')
 *         )[true] ?? 0;
 *     });
 *
 *
 * ============================================================================
 * ADVANCED PATTERNS
 * ============================================================================
 *
 * // 12. PROMISE-STYLE CHAINING
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withEmail('user@example.com')
 *     ->then(function($license) {
 *         // This runs after successful activation
 *         $license->syncAll();
 *     })
 *     ->catch(function($error) {
 *         // This runs on any error
 *         error_log('License error: ' . $error->get_error_message());
 *     })
 *     ->activate()
 *     ->send();
 *
 *
 * // 13. TAPPING FOR DEBUGGING
 * // -------------------------------------------------------------------------
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->tap(function($license) {
 *         error_log('Before validation: ' . print_r($license, true));
 *     })
 *     ->validate()
 *     ->tap(function($license) {
 *         error_log('After validation: ' . print_r($license->getResult(), true));
 *     });
 *
 *
 * // 14. STORING TEMPORARY DATA IN CHAIN
 * // -------------------------------------------------------------------------
 * $result = LicenseInfo::make()
 *     ->store('start_time', microtime(true))
 *     ->checkIps($ips)
 *     ->store('end_time', microtime(true))
 *     ->map(function($license) {
 *         $duration = $license->recall('end_time') - $license->recall('start_time');
 *         return [
 *             'results' => $license->getResult(),
 *             'duration' => $duration
 *         ];
 *     });
 *
 *
 * // 15. QUEUE OPERATIONS FOR LATER PROCESSING
 * // -------------------------------------------------------------------------
 * // Add to queue
 * LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->async(true)
 *     ->reportHack($hack_data);
 *
 * // Process queue later (typically via cron)
 * add_action('init', ['UltraguardPro\core\LicenseInfo', 'processSyncQueue']);
 *
 *
 * ============================================================================
 * STATIC HELPER METHODS
 * ============================================================================
 *
 * // 16. GET LICENSE STATUS
 * // -------------------------------------------------------------------------
 * $status = LicenseInfo::instance()->getStatus();
 * echo "Plan: {$status['plan']}, Valid: " . ($status['is_valid'] ? 'Yes' : 'No');
 *
 *
 * // 17. GET UPDATE DELAY FOR FREE USERS
 * // -------------------------------------------------------------------------
 * $delay = LicenseInfo::instance()->getUpdateDelay();
 * if ($delay > 0) {
 *     echo "Free users get updates after $delay days";
 * }
 *
 *
 * // 18. INITIALIZE WITH CUSTOM LOGGING
 * // -------------------------------------------------------------------------
 * LicenseInfo::init(
 *     function($level, $message, $context) {
 *         if ($level === 'error') {
 *             error_log("[UltraGuard] $message");
 *         }
 *     },
 *     function($message, $context) {
 *         // Send to monitoring service
 *         $monitoring->alert($message, $context);
 *     }
 * );
 *
 *
 * ============================================================================
 * ERROR HANDLING EXAMPLES
 * ============================================================================
 *
 * // 19. GRACEFUL ERROR HANDLING
 * // -------------------------------------------------------------------------
 * $license = LicenseInfo::make()
 *     ->withKey('INVALID-KEY')
 *     ->validate();
 *
 * if ($license->failed()) {
 *     $error = $license->getError();
 *     // Handle specific error codes
 *     switch ($error->get_error_code()) {
 *         case 'validation_failed':
 *             // Show invalid license message
 *             break;
 *         case 'http_error':
 *             // Show connection error
 *             break;
 *     }
 * }
 *
 *
 * // 20. RETRY LOGIC WITH TIMEOUT
 * // -------------------------------------------------------------------------
 * $result = LicenseInfo::make()
 *     ->withKey('LICENSE-12345')
 *     ->withTimeout(10)
 *     ->force(true)
 *     ->tap(function($l) {
 *         $l->store('attempts', 0);
 *     })
 *     ->when(true, function($l) {
 *         $attempts = $l->recall('attempts', 0);
 *         if ($attempts < 3 && $l->failed()) {
 *             $l->store('attempts', $attempts + 1);
 *             sleep(1); // Wait before retry
 *             $l->validate(); // Retry
 *         }
 *     })
 *     ->validate()
 *     ->getResult();
 *
 * ============================================================================
 * END OF EXAMPLES
 * ============================================================================
 */

/**
 * LicenseInfo - Chainable License & Communication Manager
 *
 * Fluent, chainable interface for license management and two-way server communication.
 * Handles license validation, real-time updates, IP reputation, and security data sync.
 *
 * @package    UltraguardPro\core
 * @author     UltraGuard Pro Team
 * @version    5.2.0
 * @since      2.0.0
 */
final class LicenseInfo {

    // =========================================================================
    // CONSTANTS
    // =========================================================================

    /** API endpoint base URL */
    const API_URL = 'https://ultraguard.net/wp-json/ultraguard/v2';
    const API_URL_V2 = 'https://ultraguard.net/wp-json/ultraguard/v2';
    const SERVER_BASE_URL = 'https://ultraguard.net';
    const SERVER_API_NAMESPACE = 'ultraguard/v2';

    /** License endpoints */
    const ENDPOINT_VALIDATE = '/license/validate';
    const ENDPOINT_ACTIVATE = '/license/activate';
    const ENDPOINT_DEACTIVATE = '/license/deactivate';
    const ENDPOINT_STATUS = '/license/status';

    /** Signature endpoints */
    const ENDPOINT_SIGNATURES = '/signatures/latest';
    const ENDPOINT_SIGNATURE_UPDATE = '/signatures/update';
    const ENDPOINT_SIGNATURE_VERSION = '/signatures/version';

    /** IP reputation endpoints */
    const ENDPOINT_IP_CHECK = '/ip/check';
    const ENDPOINT_IP_CHECK_BULK = '/ip/check-bulk';
    const ENDPOINT_IP_REPORT = '/ip/report';
    const ENDPOINT_BLACKLIST = '/ip/blacklist';
    const ENDPOINT_WHITELIST = '/ip/whitelist';
    const ENDPOINT_BLACKLIST_SYNC = '/ip/blacklist/sync';

    /** Firewall endpoints */
    const ENDPOINT_FIREWALL_RULES = '/firewall/rules';
    const ENDPOINT_FIREWALL_UPDATE = '/firewall/update';
    const ENDPOINT_FIREWALL_SYNC = '/firewall/sync';

    /** Reporting endpoints */
    const ENDPOINT_REPORT_HACK = '/report/hack';
    const ENDPOINT_REPORT_SCAN = '/report/scan';
    const ENDPOINT_REPORT_STATS = '/report/stats';
    const ENDPOINT_REPORT_BATCH = '/report/batch';

    /** Data sync endpoints */
    const ENDPOINT_SYNC_ALL = '/sync/all';
    const ENDPOINT_SYNC_STATUS = '/sync/status';
    const ENDPOINT_HEARTBEAT = '/sync/heartbeat';
    const ENDPOINT_SITE_REGISTER = '/site/register';
    const ENDPOINT_MESSAGES = '/messages';
    const ENDPOINT_MESSAGES_ACK = '/messages/ack';

    /** Option names */
    const OPTION_KEY = 'ultraguard_license_key';
    const OPTION_EMAIL = 'ultraguard_license_email';
    const OPTION_DATA = 'ultraguard_license_data';
    const OPTION_SIGNATURES = 'ultraguard_signatures';
    const OPTION_SIGNATURE_VERSION = 'ultraguard_signature_version';
    const OPTION_BLACKLIST = 'ultraguard_blacklist';
    const OPTION_BLACKLIST_HASH = 'ultraguard_blacklist_hash';
    const OPTION_WHITELIST = 'ultraguard_whitelist';
    const OPTION_FIREWALL_RULES = 'ultraguard_firewall_rules';
    const OPTION_FIREWALL_HASH = 'ultraguard_firewall_hash';
    const OPTION_COMM_STATS = 'ultraguard_comm_stats';
    const OPTION_INSTALLATION = 'ultraguard_installation_time';
    const OPTION_LAST_SYNC = 'ultraguard_last_sync';
    const OPTION_SYNC_QUEUE = 'ultraguard_sync_queue';
    const OPTION_SUBSCRIBER = 'ultraguard_remote_subscriber';
    const OPTION_MESSAGES = 'ultraguard_remote_messages';
    const OPTION_MESSAGE_ACKS = 'ultraguard_remote_message_acks';

    /** Transient names */
    const TRANSIENT_STATUS = 'ultraguard_license_status';
    const TRANSIENT_CHECK = 'ultraguard_license_check';
    const TRANSIENT_SIGNATURES = 'ultraguard_signatures_cache';
    const TRANSIENT_BLACKLIST = 'ultraguard_blacklist_cache';
    const TRANSIENT_FIREWALL = 'ultraguard_firewall_cache';
    const TRANSIENT_REGISTER_RETRY = 'ultraguard_remote_register_retry';
    const HOOK_REGISTER_INSTALLATION = 'ultraguard_register_installation';
    const HOOK_REGISTER_INSTALLATION_RETRY = 'ultraguard_register_installation_retry';

    /** Cache durations */
    const CACHE_LICENSE = 14400;        // 4 hours
    const CACHE_SIGNATURES = 86400;     // 24 hours
    const CACHE_BLACKLIST = 3600;       // 1 hour
    const CACHE_FIREWALL = 7200;        // 2 hours

    /** Update delay for free plan (days) */
    const FREE_UPDATE_DELAY = 1;

    /** Max retry attempts */
    const MAX_RETRIES = 3;

    /** Batch size for bulk operations */
    const BATCH_SIZE = 100;

    // =========================================================================
    // PROPERTIES - Instance state
    // =========================================================================

    /** @var string License key */
    private string $license_key = '';

    /** @var string License email */
    private string $license_email = '';

    /** @var array Additional data for requests */
    private array $data = [];

    /** @var array Metadata for current operation */
    private array $meta = [];

    /** @var array Statistics for reporting */
    private array $stats = [];

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

    /** @var bool Whether to force bypass cache */
    private bool $force = false;

    /** @var bool Whether to run async */
    private bool $async = false;

    /** @var bool Whether to enable batch mode */
    private bool $batch_mode = false;

    /** @var array Batch operations queue */
    private array $batch_queue = [];

    /** @var array Batch results */
    private array $batch_results = [];

    /** @var callable|null Success callback */
    private $success_callback = null;

    /** @var callable|null Error callback */
    private $error_callback = null;

    /** @var callable|null Then callback (promise-style) */
    private $then_callback = null;

    /** @var callable|null Catch callback (promise-style) */
    private $catch_callback = null;

    /** @var array Last operation result */
    private array $last_result = [];

    /** @var \WP_Error|null Last error */
    private ?\WP_Error $last_error = null;

    /** @var int Retry count for current operation */
    private int $retry_count = 0;

    /** @var bool Whether to use cache */
    private bool $use_cache = true;

    /** @var string Current operation */
    private string $current_operation = '';

    /** @var array Temporary storage for chaining */
    private array $temp_storage = [];

    // =========================================================================
    // STATIC PROPERTIES
    // =========================================================================

    /** @var LicenseInfo|null Singleton instance */
    private static ?LicenseInfo $instance = null;

    /** @var array Per-request cache */
    private static array $cache = [];

    /** @var callable|null Logger */
    private static $logger = null;

    /** @var callable|null Error handler */
    private static $error_handler = null;

    /** @var bool Whether initialized */
    private static bool $initialized = false;

    /** @var bool Flag to prevent recursion during initialization */
    private static bool $initializing = false;

    // =========================================================================
    // CONSTRUCTOR & FACTORY
    // =========================================================================

    /**
     * Private constructor - minimal initialization only
     */
    private function __construct() {
        // Don't call reset() here to avoid recursion
        // Just set default values without DB calls
        $this->timeout = 15;
        $this->use_cache = true;
    }

    /**
     * Get singleton instance - lazy initialization
     *
     * @return self
     */
    public static function instance(): self {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Create a new instance for chaining
     *
     * @return self
     */
    public static function make(): self {
        $instance = new self();
        $instance->reset(); // Reset after construction
        return $instance;
    }

    /**
     * Initialize the system
     *
     * @param callable|null $logger
     * @param callable|null $error_handler
     * @return void
     */
    public static function init($logger = null, $error_handler = null): void {
        // Prevent recursion
        if (self::$initializing) {
            return;
        }

        self::$initializing = true;

        if (self::$initialized) {
            self::$initializing = false;
            return;
        }

        self::$logger = $logger;
        self::$error_handler = $error_handler;

        // Only schedule if not already scheduled and in WordPress context
        if (function_exists('wp_next_scheduled')) {
            if (!wp_next_scheduled('ultraguard_heartbeat')) {
                wp_schedule_event(time(), 'hourly', 'ultraguard_heartbeat');
            }

            if (!wp_next_scheduled('ultraguard_sync_signatures')) {
                wp_schedule_event(time(), 'twicedaily', 'ultraguard_sync_signatures');
            }

            if (!wp_next_scheduled('ultraguard_sync_blacklist')) {
                wp_schedule_event(time(), 'hourly', 'ultraguard_sync_blacklist');
            }

            if ( ! wp_next_scheduled( self::HOOK_REGISTER_INSTALLATION_RETRY ) ) {
                wp_schedule_event( time() + ( 15 * MINUTE_IN_SECONDS ), 'hourly', self::HOOK_REGISTER_INSTALLATION_RETRY );
            }
        }

        // Use static methods for scheduled events
        add_action('ultraguard_heartbeat', [__CLASS__, 'heartbeat_static']);
        add_action( self::HOOK_REGISTER_INSTALLATION, [__CLASS__, 'registerInstallation_static'] );
        add_action( self::HOOK_REGISTER_INSTALLATION_RETRY, [__CLASS__, 'registerInstallationRetry_static'] );
        add_action('ultraguard_refresh_license_status', [__CLASS__, 'refreshStatus_static']);
        add_action('ultraguard_sync_signatures', [__CLASS__, 'syncSignatures_static']);
        add_action('ultraguard_sync_blacklist', [__CLASS__, 'syncBlacklist_static']);
        add_action('init', [__CLASS__, 'processSyncQueue_static']);
        add_action('init', [__CLASS__, 'maybeRegisterInstallation_static'], 20);
        add_action('admin_init', [__CLASS__, 'handleMessageAck_static']);
        add_action('admin_notices', [__CLASS__, 'renderServerMessages_static']);

        self::$initialized = true;
        self::$initializing = false;
    }

    // =========================================================================
    // STATIC SCHEDULED EVENT HANDLERS
    // =========================================================================

    /**
     * Static heartbeat handler for scheduled events
     *
     * @return void
     */
    public static function heartbeat_static(): void {
        $instance = self::instance();
        $instance->heartbeat();
    }

    /**
     * Static background license-status refresh handler.
     *
     * @return void
     */
    public static function refreshStatus_static(): void {
        $instance = self::instance();

        if ( ! $instance->isRemoteStatusRefreshDue() ) {
            return;
        }

        $status = $instance->refreshRemoteStatus();
        if ( is_array( $status ) && function_exists( 'set_transient' ) ) {
            self::$cache['license_status'] = $status;
            set_transient( self::TRANSIENT_STATUS, $status, self::CACHE_LICENSE );
        }
    }

    /**
     * Static install-registration handler for one-time activation retries.
     *
     * @return void
     */
    public static function registerInstallation_static(): void {
        if ( ! function_exists( 'get_option' ) || ! function_exists( 'set_transient' ) ) {
            return;
        }

        if ( get_transient( self::TRANSIENT_REGISTER_RETRY ) ) {
            return;
        }

        $instance = self::make()->registerInstallation();

        if ( $instance->succeeded() ) {
            delete_transient( self::TRANSIENT_REGISTER_RETRY );
            return;
        }

        set_transient( self::TRANSIENT_REGISTER_RETRY, 1, HOUR_IN_SECONDS );
    }

    /**
     * Static recurring install-registration retry handler.
     *
     * @return void
     */
    public static function registerInstallationRetry_static(): void {
        if ( ! self::shouldAttemptInstallationRegistration( true ) ) {
            return;
        }

        self::registerInstallation_static();
    }

    /**
     * Static sync signatures handler for scheduled events
     *
     * @return void
     */
    public static function syncSignatures_static(): void {
        $instance = self::instance();
        $instance->syncSignatures();
    }

    /**
     * Static sync blacklist handler for scheduled events
     *
     * @return void
     */
    public static function syncBlacklist_static(): void {
        $instance = self::instance();
        $instance->syncBlacklist();
    }

    /**
     * Static process queue handler
     *
     * @return void
     */
    public static function processSyncQueue_static(): void {
        self::processSyncQueue();
    }

    /**
     * Attempt site registration for free/pro installs on normal requests.
     *
     * @return void
     */
    public static function maybeRegisterInstallation_static(): void {
        if ( ! function_exists( 'get_option' ) || ! function_exists( 'set_transient' ) ) {
            return;
        }

        self::ensureRegistrationRetryScheduled();

        if ( ! self::shouldAttemptInstallationRegistration() ) {
            return;
        }

        if ( ! wp_next_scheduled( self::HOOK_REGISTER_INSTALLATION ) ) {
            wp_schedule_single_event( time() + MINUTE_IN_SECONDS, self::HOOK_REGISTER_INSTALLATION );
        }
    }

    /**
     * Handle manual message acknowledgements from the admin notice area.
     *
     * @return void
     */
    public static function handleMessageAck_static(): void {
        if ( ! is_admin() || ! function_exists( '\\UltraguardPro\\ultraguard_current_user_can_manage' ) || ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            return;
        }

        $message_key = sanitize_key( wp_unslash( (string) ( $_GET['ultraguard_ack_message'] ?? '' ) ) );
        if ( '' === $message_key ) {
            return;
        }

        check_admin_referer( 'ultraguard_ack_message_' . $message_key );

        $dismissed = get_option( self::OPTION_MESSAGE_ACKS, [] );
        if ( ! is_array( $dismissed ) ) {
            $dismissed = [];
        }

        if ( ! in_array( $message_key, $dismissed, true ) ) {
            $dismissed[] = $message_key;
            update_option( self::OPTION_MESSAGE_ACKS, array_values( array_unique( $dismissed ) ), false );
        }

        self::make()->acknowledgeMessage( $message_key );

        wp_safe_redirect( remove_query_arg( [ 'ultraguard_ack_message', '_wpnonce' ] ) );
        exit;
    }

    /**
     * Render active server-driven notices inside wp-admin.
     *
     * @return void
     */
    public static function renderServerMessages_static(): void {
        if ( ! is_admin() || ! function_exists( '\\UltraguardPro\\ultraguard_current_user_can_manage' ) || ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            return;
        }

        $messages  = get_option( self::OPTION_MESSAGES, [] );
        $dismissed = get_option( self::OPTION_MESSAGE_ACKS, [] );

        if ( ! is_array( $messages ) || empty( $messages ) ) {
            return;
        }

        if ( ! is_array( $dismissed ) ) {
            $dismissed = [];
        }

        foreach ( $messages as $message ) {
            if ( ! is_array( $message ) ) {
                continue;
            }

            $message_key = sanitize_key( (string) ( $message['message_key'] ?? '' ) );
            if ( '' !== $message_key && in_array( $message_key, $dismissed, true ) ) {
                continue;
            }

            $title       = sanitize_text_field( (string) ( $message['title'] ?? '' ) );
            $body        = wp_kses_post( (string) ( $message['body'] ?? '' ) );
            $message_type = sanitize_key( (string) ( $message['message_type'] ?? 'notification' ) );
            $cta_label   = sanitize_text_field( (string) ( $message['cta_label'] ?? '' ) );
            $cta_url     = esc_url( (string) ( $message['cta_url'] ?? '' ) );
            $dismissible = ! empty( $message['dismissible'] );

            $notice_class = 'notice-info';
            if ( in_array( $message_type, [ 'promotion', 'upgrade', 'license-info' ], true ) ) {
                $notice_class = 'notice-warning';
            } elseif ( in_array( $message_type, [ 'security', 'critical' ], true ) ) {
                $notice_class = 'notice-error';
            } elseif ( 'success' === $message_type ) {
                $notice_class = 'notice-success';
            }

            echo '<div class="notice ' . esc_attr( $notice_class ) . ( $dismissible ? ' is-dismissible' : '' ) . ' ultraguard-server-message">';
            if ( '' !== $title ) {
                echo '<p><strong>' . esc_html( $title ) . '</strong></p>';
            }
            if ( '' !== $body ) {
                echo '<p>' . esc_html( $body ) . '</p>';
            }

            if ( '' !== $cta_label && '' !== $cta_url ) {
                echo '<p><a class="button button-primary" href="' . esc_url( $cta_url ) . '" target="_blank" rel="noreferrer noopener">' . esc_html( $cta_label ) . '</a></p>';
            }

            if ( $dismissible && '' !== $message_key ) {
                $dismiss_url = wp_nonce_url(
                    add_query_arg( 'ultraguard_ack_message', $message_key ),
                    'ultraguard_ack_message_' . $message_key
                );
                echo '<p><a href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Dismiss this message', 'ultraguard-security-suite' ) . '</a></p>';
            }

            echo '</div>';
        }
    }

    // =========================================================================
    // FLUENT SETTERS
    // =========================================================================

    /**
     * Set license key
     *
     * @param string $key
     * @return self
     */
    public function withKey(string $key): self {
        $key = sanitize_text_field(trim($key));

        if ( function_exists( 'get_option' ) && $key !== (string) get_option( self::OPTION_KEY, '' ) ) {
            $this->invalidateCachedStatus();
        }

        $this->license_key = $key;
        return $this;
    }

    /**
     * Set license email
     *
     * @param string $email
     * @return self
     */
    public function withEmail(string $email): self {
        $email = sanitize_email($email);

        if ( function_exists( 'get_option' ) && $email !== (string) get_option( self::OPTION_EMAIL, '' ) ) {
            $this->invalidateCachedStatus();
        }

        $this->license_email = $email;
        return $this;
    }

    /**
     * Set additional data
     *
     * @param array $data
     * @return self
     */
    public function withData(array $data): self {
        $this->data = array_merge($this->data, $data);
        return $this;
    }

    /**
     * Add a single data item
     *
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function with(string $key, $value): self {
        $this->data[$key] = $value;
        return $this;
    }

    /**
     * Set metadata
     *
     * @param array $meta
     * @return self
     */
    public function withMeta(array $meta): self {
        $this->meta = array_merge($this->meta, $meta);
        return $this;
    }

    /**
     * Add a single metadata item
     *
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function meta(string $key, $value): self {
        $this->meta[$key] = $value;
        return $this;
    }

    /**
     * Set statistics for reporting
     *
     * @param array $stats
     * @return self
     */
    public function withStats(array $stats): self {
        $this->stats = array_merge($this->stats, $stats);
        return $this;
    }

    /**
     * Add a single statistic
     *
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function stat(string $key, $value): self {
        $this->stats[$key] = $value;
        return $this;
    }

    /**
     * Set request timeout
     *
     * @param int $seconds
     * @return self
     */
    public function withTimeout(int $seconds): self {
        $this->timeout = max(1, min(60, $seconds));
        return $this;
    }

    /**
     * Force operation (bypass cache)
     *
     * @param bool $force
     * @return self
     */
    public function force(bool $force = true): self {
        $this->force = $force;
        $this->use_cache = !$force;
        return $this;
    }

    /**
     * Disable cache for this operation
     *
     * @return self
     */
    public function noCache(): self {
        $this->use_cache = false;
        $this->force = true;
        return $this;
    }

    /**
     * Run operation asynchronously
     *
     * @param bool $async
     * @return self
     */
    public function async(bool $async = true): self {
        $this->async = $async;
        return $this;
    }

    /**
     * Enable batch mode
     *
     * @param bool $batch
     * @return self
     */
    public function batch(bool $batch = true): self {
        $this->batch_mode = $batch;
        return $this;
    }

    /**
     * Set success callback
     *
     * @param callable $callback
     * @return self
     */
    public function onSuccess(callable $callback): self {
        $this->success_callback = $callback;
        return $this;
    }

    /**
     * Set error callback
     *
     * @param callable $callback
     * @return self
     */
    public function onError(callable $callback): self {
        $this->error_callback = $callback;
        return $this;
    }

    /**
     * Set then callback (promise-style)
     *
     * @param callable $callback
     * @return self
     */
    public function then(callable $callback): self {
        $this->then_callback = $callback;
        return $this;
    }

    /**
     * Set catch callback (promise-style)
     *
     * @param callable $callback
     * @return self
     */
    public function catch(callable $callback): self {
        $this->catch_callback = $callback;
        return $this;
    }

    /**
     * Conditional execution
     *
     * @param bool $condition
     * @param callable $callback
     * @return self
     */
    public function when(bool $condition, callable $callback): self {
        if ($condition) {
            $callback($this);
        }
        return $this;
    }

    /**
     * Otherwise conditional execution
     *
     * @param callable $callback
     * @return self
     */
    public function otherwise(callable $callback): self {
        if (!$this->temp_storage['last_condition'] ?? false) {
            $callback($this);
        }
        return $this;
    }

    /**
     * Map results through callback
     *
     * @param callable $callback
     * @return mixed
     */
    public function map(callable $callback) {
        return $callback($this->last_result);
    }

    /**
     * Tap into the chain (for debugging)
     *
     * @param callable $callback
     * @return self
     */
    public function tap(callable $callback): self {
        $callback($this);
        return $this;
    }

    /**
     * Store value in temporary storage
     *
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function store(string $key, $value): self {
        $this->temp_storage[$key] = $value;
        return $this;
    }

    /**
     * Retrieve from temporary storage
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    public function recall(string $key, $default = null) {
        return $this->temp_storage[$key] ?? $default;
    }

    /**
     * Reset instance to default state
     *
     * @return self
     */
    public function reset(): self {
        // Only load options if WordPress is fully loaded
        if (function_exists('get_option')) {
            $this->license_key = get_option(self::OPTION_KEY, '');
            $this->license_email = get_option(self::OPTION_EMAIL, '');
        } else {
            $this->license_key = '';
            $this->license_email = '';
        }

        $this->data = [];
        $this->meta = [];
        $this->stats = [];
        $this->timeout = 15;
        $this->force = false;
        $this->async = false;
        $this->batch_mode = false;
        $this->batch_queue = [];
        $this->batch_results = [];
        $this->success_callback = null;
        $this->error_callback = null;
        $this->then_callback = null;
        $this->catch_callback = null;
        $this->last_result = [];
        $this->last_error = null;
        $this->retry_count = 0;
        $this->use_cache = true;
        $this->current_operation = '';
        $this->temp_storage = [];

        return $this;
    }

    // =========================================================================
    // LICENSE OPERATIONS
    // =========================================================================

    /**
     * Check if Pro license is active
     *
     * @return bool
     */
    public function isPro(): bool {
        $status = $this->getStatus();
        return (bool) ( $status['is_pro'] ?? false );
    }

    /**
     * Check if the active license is an Agency tier.
     *
     * @return bool
     */
    public function isAgency(): bool {
        $status = $this->getStatus();
        $plan   = sanitize_key( (string) ( $status['plan'] ?? 'free' ) );

        return (bool) ( $status['is_pro'] ?? false )
            && in_array( $plan, [ 'agency', 'agency_pro', 'agency-plan', 'agency_plan' ], true );
    }

    /**
     * Check if license is valid
     *
     * @return bool
     */
    public function isValid(): bool {
        $status = $this->getStatus();
        return $status['is_valid'] ?? false;
    }

    /**
     * Get update delay in days
     *
     * @return int
     */
    public function getUpdateDelay(): int {
        $data = get_option( self::OPTION_DATA, [] );
        $delay = isset( $data['free_delay_days'] ) ? (int) $data['free_delay_days'] : self::FREE_UPDATE_DELAY;

        return $this->isPro() ? 0 : max( 0, $delay );
    }

    /**
     * Validate license with server
     *
     * @return self
     */
    public function validate(): self {
        $this->current_operation = 'validate';

        if ($this->async) {
            return $this->queueAsync('validate', []);
        }

        $this->ensureCredentialsLoaded();

        if ( '' === $this->license_key ) {
            $this->persistFreeState( 'local_free' );
            $this->handleSuccess( [
                'success' => true,
                'status'  => 'free',
                'plan'    => 'free',
                'is_pro'  => false,
                'message' => __( 'No Pro license is configured. UltraGuard is running in Free mode.', 'ultraguard-security-suite' ),
            ] );

            return $this;
        }

        $result = $this->callApi(self::ENDPOINT_VALIDATE, [
            'license_key' => $this->license_key,
            'email' => $this->license_email,
            'site_url' => home_url(),
        ]);

        if ($result['success']) {
            $stored = get_option( self::OPTION_DATA, [] );
            update_option(self::OPTION_DATA, array_merge( is_array( $stored ) ? $stored : [], is_array( $result['data'] ?? null ) ? $result['data'] : [] ) );
            CacheManager::get_instance()->set('ug_license_status', (string) ( $result['data']['status'] ?? 'valid' ), 'asset');
            $this->invalidateCachedStatus();
            $this->handleSuccess($result);
        } else {
            $this->maybeDowngradeToFree( $result, 'validate' );
            $this->handleError(new \WP_Error('validation_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Activate license
     *
     * @return self
     */
    public function activate(): self {
        $this->current_operation = 'activate';

        $this->ensureCredentialsLoaded();

        if (empty($this->license_key)) {
            return $this->handleError(new \WP_Error('no_key', 'License key required'));
        }

        $result = $this->callApi(self::ENDPOINT_ACTIVATE, array_merge([
            'license_key' => $this->license_key,
            'email' => $this->license_email,
            'site_url' => home_url(),
            'site_name' => get_bloginfo('name'),
            'wp_version' => get_bloginfo('version'),
            'php_version' => PHP_VERSION,
        ], $this->data));

        if ($result['success']) {
            update_option(self::OPTION_KEY, $this->license_key);
            update_option(self::OPTION_EMAIL, $this->license_email);
            $stored = get_option( self::OPTION_DATA, [] );
            update_option(self::OPTION_DATA, array_merge( is_array( $stored ) ? $stored : [], is_array( $result['data'] ?? null ) ? $result['data'] : [] ) );
            CacheManager::get_instance()->set('ug_license_status', 'valid', 'asset');
            $this->invalidateCachedStatus();

            $this->handleSuccess($result);

            // Trigger post-activation sync
            if (!$this->batch_mode) {
                delete_transient( self::TRANSIENT_REGISTER_RETRY );
                $this->registerInstallation();
                $this->syncAll();
            }
        } else {
            $this->maybeDowngradeToFree( $result, 'activate' );
            $this->handleError(new \WP_Error('activation_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Deactivate license
     *
     * @return self
     */
    public function deactivate(): self {
        $this->current_operation = 'deactivate';

        $this->ensureCredentialsLoaded();

        if ( '' === $this->license_key ) {
            $this->persistFreeState( 'deactivate' );
            $this->handleSuccess( [
                'success' => true,
                'status'  => 'free',
                'plan'    => 'free',
                'is_pro'  => false,
                'message' => __( 'No active Pro license is configured. UltraGuard is already running in Free mode.', 'ultraguard-security-suite' ),
            ] );

            return $this;
        }

        $result = $this->callApi(self::ENDPOINT_DEACTIVATE, [
            'license_key' => $this->license_key,
            'email' => $this->license_email,
            'site_url' => home_url(),
        ]);

        if ( empty( $result['success'] ) ) {
            return $this->handleError(
                new \WP_Error(
                    'deactivation_failed',
                    (string) ( $result['message'] ?? __( 'Unable to deactivate the license. The local Pro state was kept so you can retry safely.', 'ultraguard-security-suite' ) )
                )
            );
        }

        $this->persistFreeState( 'deactivate' );
        $this->handleSuccess( [
            'success' => true,
            'status'  => 'free',
            'plan'    => 'free',
            'is_pro'  => false,
            'message' => __( 'License deactivated.', 'ultraguard-security-suite' ),
        ] );

        return $this;
    }

    /**
     * Get license status
     *
     * @return array
     */
    public function getStatus(): array {
        $cache_key = 'license_status';

        $this->ensureCredentialsLoaded();
        $forced = $this->force;
        $this->force = false;

        if ( ! $forced && isset(self::$cache[$cache_key]) ) {
            return self::$cache[$cache_key];
        }

        // Check if WordPress functions are available
        if (!function_exists('get_transient')) {
            return [
                'status' => 'unknown',
                'plan' => 'free',
                'is_pro' => false,
                'is_valid' => false,
                'license_key' => '',
                'email' => '',
                'data' => [],
                'update_delay' => self::FREE_UPDATE_DELAY,
            ];
        }

        if ( ! $forced ) {
            $cached_status = get_transient( self::TRANSIENT_STATUS );
            if ( is_array( $cached_status ) ) {
                if ( $this->isRemoteStatusRefreshDue() ) {
                    $this->scheduleStatusRefresh();
                }
                self::$cache[$cache_key] = $cached_status;
                return self::$cache[$cache_key];
            }
        }

        if ( $forced && $this->isRemoteStatusRefreshDue() ) {
            $refreshed = $this->refreshRemoteStatus();
            if ( is_array( $refreshed ) ) {
                self::$cache[$cache_key] = $refreshed;
                set_transient( self::TRANSIENT_STATUS, self::$cache[$cache_key], self::CACHE_LICENSE );
                return self::$cache[$cache_key];
            }
        } elseif ( $forced ) {
            $cached_status = get_transient( self::TRANSIENT_STATUS );
            if ( is_array( $cached_status ) ) {
                self::$cache[$cache_key] = $cached_status;
                return self::$cache[$cache_key];
            }
        }

        self::$cache[$cache_key] = $this->buildStatusSnapshot();

        set_transient( self::TRANSIENT_STATUS, self::$cache[$cache_key], self::CACHE_LICENSE );

        if ( $this->isRemoteStatusRefreshDue() ) {
            $this->scheduleStatusRefresh();
        }

        return self::$cache[$cache_key];
    }

    // =========================================================================
    // SIGNATURE OPERATIONS
    // =========================================================================

    /**
     * Sync antivirus signatures
     *
     * @return self
     */
    public function syncSignatures(): self {
        $this->current_operation = 'sync_signatures';

        if ($this->async) {
            return $this->queueAsync('syncSignatures', []);
        }

        // Check cache
        if (!$this->force && $this->use_cache) {
            $cached = CacheManager::get_instance()->get('ug_license_signatures', 'asset');
            if ($cached !== false) {
                $this->last_result = ['success' => true, 'cached' => true, 'data' => $cached];
                return $this;
            }
        }

        $current_version = get_option(self::OPTION_SIGNATURE_VERSION, '0');

        $result = $this->callApi(self::ENDPOINT_SIGNATURES, [
            'license_key' => $this->license_key,
            'current_version' => $current_version,
            'delay_days' => $this->getUpdateDelay(),
        ]);

        if ($result['success']) {
            if (!empty($result['signatures'])) {
                update_option(self::OPTION_SIGNATURES, $result['signatures']);
                update_option(self::OPTION_SIGNATURE_VERSION, $result['version']);
                CacheManager::get_instance()->set('ug_license_signatures', $result['signatures'], 'asset');

                $this->last_result = [
                    'success' => true,
                    'version' => $result['version'],
                    'count' => count($result['signatures']),
                    'updated' => true,
                ];
            } else {
                $this->last_result = ['success' => true, 'updated' => false];
            }

            $this->handleSuccess($this->last_result);
        } else {
            $this->handleError(new \WP_Error('signature_sync_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Get current signatures
     *
     * @return array
     */
    public function getSignatures(): array {
        if (!$this->force && $this->use_cache) {
            $cached = CacheManager::get_instance()->get('ug_license_signatures', 'asset');
            if ($cached !== false) {
                return $cached;
            }
        }

        $signatures = get_option(self::OPTION_SIGNATURES, []);
        CacheManager::get_instance()->set('ug_license_signatures', $signatures, 'asset');

        return $signatures;
    }

    // =========================================================================
    // IP REPUTATION OPERATIONS
    // =========================================================================

    /**
     * Check if IP is blacklisted
     *
     * @param string $ip
     * @return bool
     */
    public function isIpBlacklisted(string $ip): bool {
        $result = $this->checkIp($ip)->getResult();
        return $result['is_blacklisted'] ?? false;
    }

    /**
     * Check if IP is whitelisted
     *
     * @param string $ip
     * @return bool
     */
    public function isIpWhitelisted(string $ip): bool {
        $whitelist = $this->getWhitelist();
        return in_array($ip, $whitelist, true);
    }

    /**
     * Check IP reputation
     *
     * @param string $ip
     * @return self
     */
    public function checkIp(string $ip): self {
        $this->current_operation = 'check_ip';

        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return $this->handleError(new \WP_Error('invalid_ip', 'Invalid IP address'));
        }

        // Check local cache first
        $blacklist = $this->getBlacklist();
        if (in_array($ip, $blacklist, true)) {
            $this->last_result = [
                'success' => true,
                'ip' => $ip,
                'is_blacklisted' => true,
                'source' => 'cache',
            ];
            return $this;
        }

        $whitelist = $this->getWhitelist();
        if (in_array($ip, $whitelist, true)) {
            $this->last_result = [
                'success' => true,
                'ip' => $ip,
                'is_blacklisted' => false,
                'is_whitelisted' => true,
                'source' => 'cache',
            ];
            return $this;
        }

        // Check with server
        $result = $this->callApi(self::ENDPOINT_IP_CHECK, [
            'license_key' => $this->license_key,
            'ip' => $ip,
        ]);

        if ($result['success']) {
            $this->last_result = [
                'success' => true,
                'ip' => $ip,
                'is_blacklisted' => $result['is_blacklisted'] ?? false,
                'reputation' => $result['reputation'] ?? 0,
                'categories' => $result['categories'] ?? [],
                'source' => 'api',
            ];

            // Update local cache
            if ($this->last_result['is_blacklisted']) {
                $this->addToBlacklist($ip);
            }

            $this->handleSuccess($this->last_result);
        } else {
            $this->handleError(new \WP_Error('ip_check_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Check multiple IPs in bulk
     *
     * @param array $ips
     * @return self
     */
    public function checkIps(array $ips): self {
        $this->current_operation = 'check_ips';

        $valid_ips = array_filter($ips, function($ip) {
            return filter_var($ip, FILTER_VALIDATE_IP);
        });

        if (empty($valid_ips)) {
            return $this->handleError(new \WP_Error('no_valid_ips', 'No valid IP addresses'));
        }

        // Check local cache first
        $blacklist = $this->getBlacklist();
        $whitelist = $this->getWhitelist();

        $results = [];
        $api_ips = [];

        foreach ($valid_ips as $ip) {
            if (in_array($ip, $blacklist, true)) {
                $results[$ip] = ['is_blacklisted' => true, 'source' => 'cache'];
            } elseif (in_array($ip, $whitelist, true)) {
                $results[$ip] = ['is_blacklisted' => false, 'is_whitelisted' => true, 'source' => 'cache'];
            } else {
                $api_ips[] = $ip;
            }
        }

        // Check remaining IPs with server
        if (!empty($api_ips)) {
            $chunks = array_chunk($api_ips, self::BATCH_SIZE);

            foreach ($chunks as $chunk) {
                $result = $this->callApi(self::ENDPOINT_IP_CHECK_BULK, [
                    'license_key' => $this->license_key,
                    'ips' => $chunk,
                ]);

                if ($result['success'] && isset($result['results'])) {
                    foreach ($result['results'] as $ip => $data) {
                        $results[$ip] = array_merge($data, ['source' => 'api']);

                        // Update local cache
                        if ($data['is_blacklisted'] ?? false) {
                            $this->addToBlacklist($ip);
                        }
                    }
                }
            }
        }

        $this->last_result = [
            'success' => true,
            'results' => $results,
            'total' => count($results),
        ];

        $this->handleSuccess($this->last_result);

        return $this;
    }

    /**
     * Report a hack attempt
     *
     * @param array $data
     * @return self
     */
    public function reportHack(array $data): self {
        $this->current_operation = 'report_hack';

        $report_data = array_merge([
            'license_key' => $this->license_key,
            'site_url' => home_url(),
            'timestamp' => current_time('mysql'),
            'ip' => sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ),
            'user_agent' => sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ),
        ], $data, $this->data, $this->meta);

        if ($this->async) {
            return $this->queueAsync('reportHack', [$report_data]);
        }

        $result = $this->callApi(self::ENDPOINT_REPORT_HACK, $report_data);

        if ($result['success']) {
            // If this IP is confirmed malicious, add to local blacklist
            if ($result['add_to_blacklist'] ?? false) {
                $this->addToBlacklist($data['ip'] ?? $report_data['ip']);
            }

            $this->last_result = ['success' => true, 'report_id' => $result['report_id'] ?? ''];
            $this->handleSuccess($this->last_result);
        } else {
            $this->handleError(new \WP_Error('report_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Sync blacklist with server
     *
     * @return self
     */
    public function syncBlacklist(): self {
        $this->current_operation = 'sync_blacklist';

        if ($this->async) {
            return $this->queueAsync('syncBlacklist', []);
        }

        $current_hash = get_option(self::OPTION_BLACKLIST_HASH, '');

        $result = $this->callApi(self::ENDPOINT_BLACKLIST_SYNC, [
            'license_key' => $this->license_key,
            'current_hash' => $current_hash,
        ]);

        if ($result['success']) {
            if (!empty($result['blacklist'])) {
                update_option(self::OPTION_BLACKLIST, $result['blacklist']);
                update_option(self::OPTION_BLACKLIST_HASH, $result['hash']);
                CacheManager::get_instance()->set('ug_license_blacklist', $result['blacklist'], 'asset');

                $this->last_result = [
                    'success' => true,
                    'count' => count($result['blacklist']),
                    'updated' => true,
                ];
            } else {
                $this->last_result = ['success' => true, 'updated' => false];
            }

            $this->handleSuccess($this->last_result);
        } else {
            $this->handleError(new \WP_Error('blacklist_sync_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Get blacklist
     *
     * @return array
     */
    public function getBlacklist(): array {
        if (!$this->force && $this->use_cache) {
            $cached = CacheManager::get_instance()->get('ug_license_blacklist', 'asset');
            if ($cached !== false) {
                return $cached;
            }
        }

        $blacklist = get_option(self::OPTION_BLACKLIST, []);
        CacheManager::get_instance()->set('ug_license_blacklist', $blacklist, 'asset');

        return $blacklist;
    }

    /**
     * Get whitelist
     *
     * @return array
     */
    public function getWhitelist(): array {
        return get_option(self::OPTION_WHITELIST, []);
    }

    /**
     * Add IP to local blacklist
     *
     * @param string $ip
     * @return self
     */
    public function addToBlacklist(string $ip): self {
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return $this;
        }

        $blacklist = $this->getBlacklist();
        if (!in_array($ip, $blacklist, true)) {
            $blacklist[] = $ip;
            update_option(self::OPTION_BLACKLIST, $blacklist);
            delete_transient(self::TRANSIENT_BLACKLIST);
        }

        return $this;
    }

    /**
     * Add IP to local whitelist
     *
     * @param string $ip
     * @return self
     */
    public function addToWhitelist(string $ip): self {
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return $this;
        }

        $whitelist = $this->getWhitelist();
        if (!in_array($ip, $whitelist, true)) {
            $whitelist[] = $ip;
            update_option(self::OPTION_WHITELIST, $whitelist);
        }

        return $this;
    }

    /**
     * Remove IP from blacklist
     *
     * @param string $ip
     * @return self
     */
    public function removeFromBlacklist(string $ip): self {
        $blacklist = $this->getBlacklist();
        $key = array_search($ip, $blacklist, true);
        if ($key !== false) {
            unset($blacklist[$key]);
            update_option(self::OPTION_BLACKLIST, array_values($blacklist));
            delete_transient(self::TRANSIENT_BLACKLIST);
        }

        return $this;
    }

    // =========================================================================
    // FIREWALL OPERATIONS
    // =========================================================================

    /**
     * Sync firewall rules
     *
     * @return self
     */
    public function syncFirewallRules(): self {
        $this->current_operation = 'sync_firewall';

        if ($this->async) {
            return $this->queueAsync('syncFirewallRules', []);
        }

        $current_hash = get_option(self::OPTION_FIREWALL_HASH, '');

        $result = $this->callApi(self::ENDPOINT_FIREWALL_SYNC, [
            'license_key' => $this->license_key,
            'current_hash' => $current_hash,
            'delay_days' => $this->getUpdateDelay(),
        ]);

        if ($result['success']) {
            if (!empty($result['rules'])) {
                update_option(self::OPTION_FIREWALL_RULES, $result['rules']);
                update_option(self::OPTION_FIREWALL_HASH, $result['hash']);
                CacheManager::get_instance()->set('ug_license_firewall', $result['rules'], 'asset');

                $this->last_result = [
                    'success' => true,
                    'count' => count($result['rules']),
                    'updated' => true,
                ];
            } else {
                $this->last_result = ['success' => true, 'updated' => false];
            }

            $this->handleSuccess($this->last_result);
        } else {
            $this->handleError(new \WP_Error('firewall_sync_failed', $result['message']));
        }

        return $this;
    }

    /**
     * Get firewall rules
     *
     * @return array
     */
    public function getFirewallRules(): array {
        if (!$this->force && $this->use_cache) {
            $cached = CacheManager::get_instance()->get('ug_license_firewall', 'asset');
            if ($cached !== false) {
                return $cached;
            }
        }

        $rules = get_option(self::OPTION_FIREWALL_RULES, []);
        CacheManager::get_instance()->set('ug_license_firewall', $rules, 'asset');

        return $rules;
    }

    // =========================================================================
    // BATCH OPERATIONS
    // =========================================================================

    /**
     * Sync all data
     *
     * @return self
     */
    public function syncAll(): self {
        if ($this->batch_mode) {
            $this->batch_queue[] = 'syncSignatures';
            $this->batch_queue[] = 'syncBlacklist';
            $this->batch_queue[] = 'syncFirewallRules';
            return $this;
        }

        $this->syncSignatures()
             ->syncBlacklist()
             ->syncFirewallRules();

        return $this;
    }

    /**
     * Send heartbeat
     *
     * @return self
     */
    public function heartbeat(): self {
        $this->current_operation = 'heartbeat';

        $this->ensureCredentialsLoaded();

        if ( ! $this->force && ! $this->isRemoteStatusRefreshDue() ) {
            $this->last_result = [
                'success' => true,
                'cached'  => true,
                'skipped' => true,
                'status'  => $this->buildStatusSnapshot(),
            ];

            $this->handleSuccess( $this->last_result );
            return $this;
        }

        $payload = $this->getRegistrationPayload();
        $payload['stats'] = $this->getCommunicationStats();
        $payload['version'] = defined('ULTRAGUARD_PRO_VERSION') ? ULTRAGUARD_PRO_VERSION : '1.0.0';

        $result = $this->callApi(self::ENDPOINT_HEARTBEAT, $payload);

        if ( ! empty( $result['success'] ) ) {
            update_option( self::OPTION_LAST_SYNC, time() );
            $this->handleSuccess( $result );
        } else {
            $this->maybeDowngradeToFree( $result, 'heartbeat' );
            $this->handleError(
                new \WP_Error(
                    'heartbeat_failed',
                    (string) ( $result['message'] ?? 'Unable to sync with the UltraGuard server.' )
                )
            );
        }

        return $this;
    }

    /**
     * Report statistics
     *
     * @return self
     */
    public function reportStats(): self {
        $stats = array_merge([
            'license_key' => $this->license_key,
            'site_url' => home_url(),
            'timestamp' => current_time('mysql'),
        ], $this->stats);

        if ($this->async) {
            return $this->queueAsync('reportStats', [$stats]);
        }

        $this->callApi(self::ENDPOINT_REPORT_STATS, $stats);

        return $this;
    }

    /**
     * Execute batch operations
     *
     * @return self
     */
    public function executeBatch(): self {
        $this->batch_results = [];

        foreach ($this->batch_queue as $operation) {
            $result = $this->$operation()->getResult();
            $this->batch_results[$operation] = $result;
        }

        $this->batch_queue = [];
        $this->last_result = [
            'success' => true,
            'results' => $this->batch_results,
        ];

        $this->handleSuccess($this->last_result);

        return $this;
    }

    /**
     * Get batch results
     *
     * @return array
     */
    public function getBatchResults(): array {
        return $this->batch_results;
    }

    // =========================================================================
    // QUEUE MANAGEMENT
    // =========================================================================

    /**
     * Queue async operation
     *
     * @param string $operation
     * @param array $args
     * @return self
     */
    private function queueAsync(string $operation, array $args): self {
        $queue = get_option(self::OPTION_SYNC_QUEUE, []);

        $queue[] = [
            'operation' => $operation,
            'args' => $args,
            'time' => time(),
            'retries' => 0,
        ];

        update_option(self::OPTION_SYNC_QUEUE, $queue);

        return $this;
    }

    /**
     * Process sync queue
     *
     * @return void
     */
    public static function processSyncQueue(): void {
        $queue = get_option(self::OPTION_SYNC_QUEUE, []);

        if (empty($queue)) {
            return;
        }

        $new_queue = [];
        $instance = self::instance();

        foreach ($queue as $item) {
            // Skip if too many retries
            if ($item['retries'] >= self::MAX_RETRIES) {
                self::logError("Max retries reached for operation: {$item['operation']}");
                continue;
            }

            // Execute operation
            try {
                call_user_func_array([$instance, $item['operation']], $item['args']);
            } catch (\Exception $e) {
                $item['retries']++;
                $new_queue[] = $item;
                self::logError("Queue operation failed: {$item['operation']}", $e);
            }
        }

        update_option(self::OPTION_SYNC_QUEUE, $new_queue);
    }

    // =========================================================================
    // RESULT HANDLING
    // =========================================================================

    /**
     * Get last result
     *
     * @return array
     */
    public function getResult(): array {
        return $this->last_result;
    }

    /**
     * Get last error
     *
     * @return \WP_Error|null
     */
    public function getError(): ?\WP_Error {
        return $this->last_error;
    }

    /**
     * Check if last operation was successful
     *
     * @return bool
     */
    public function succeeded(): bool {
        return ($this->last_result['success'] ?? false) && $this->last_error === null;
    }

    /**
     * Check if last operation failed
     *
     * @return bool
     */
    public function failed(): bool {
        return !$this->succeeded();
    }

    /**
     * Send the operation (execute callbacks)
     *
     * @return self
     */
    public function send(): self {
        if ($this->succeeded() && $this->success_callback) {
            call_user_func($this->success_callback, $this->last_result, $this);
        } elseif ($this->failed() && $this->error_callback) {
            call_user_func($this->error_callback, $this->last_error, $this);
        }

        // Promise-style callbacks
        if ($this->succeeded() && $this->then_callback) {
            call_user_func($this->then_callback, $this);
        } elseif ($this->failed() && $this->catch_callback) {
            call_user_func($this->catch_callback, $this->last_error, $this);
        }

        return $this;
    }

    // =========================================================================
    // STATISTICS
    // =========================================================================

    /**
     * Get communication statistics
     *
     * @return array
     */
    public function getCommunicationStats(): array {
        $stats = get_option(self::OPTION_COMM_STATS, [
            'last_sync' => null,
            'total_requests' => 0,
            'successful_requests' => 0,
            'failed_requests' => 0,
            'signature_updates' => 0,
            'ip_checks' => 0,
            'hack_reports' => 0,
        ]);

        $stats['server_base_url'] = self::serverBaseUrl();
        $stats['server_api_base'] = self::serverApiBaseUrl();
        $stats['sslverify']       = self::shouldVerifySslForUrl( self::serverBaseUrl() );

        return $stats;
    }

    /**
     * Update communication statistics
     *
     * @param string $type
     * @param bool $success
     * @return void
     */
    private function updateStats(string $type, bool $success): void {
        $stats = $this->getCommunicationStats();

        $stats['last_sync'] = time();
        $stats['total_requests']++;

        if ($success) {
            $stats['successful_requests']++;

            switch ($type) {
                case 'sync_signatures':
                    $stats['signature_updates']++;
                    break;
                case 'check_ip':
                case 'check_ips':
                    $stats['ip_checks']++;
                    break;
                case 'report_hack':
                    $stats['hack_reports']++;
                    break;
            }
        } else {
            $stats['failed_requests']++;
        }

        update_option(self::OPTION_COMM_STATS, $stats);
    }

    // =========================================================================
    // API COMMUNICATION
    // =========================================================================

    /**
     * Call API endpoint
     *
     * @param string $endpoint
     * @param array $data
     * @return array
     */
    private function callApi(string $endpoint, array $data): array {
        $this->ensureCredentialsLoaded();

        $body = array_merge($data, [
            'site_url' => home_url(),
            'site_name' => get_bloginfo('name'),
            'wp_version' => get_bloginfo('version'),
            'plugin_version' => defined('ULTRAGUARD_PRO_VERSION') ? ULTRAGUARD_PRO_VERSION : '1.0.0',
            'php_version' => PHP_VERSION,
            'timestamp' => time(),
        ]);

        if ( empty( $body['email'] ) ) {
            $body['email'] = $this->license_email ?: (string) get_option( 'admin_email', '' );
        }

        $last_failure = null;

        foreach ( self::endpointUrlCandidates( $endpoint ) as $url ) {
            $result = SecureRemoteTransport::signed_request_json(
                'POST',
                $url,
                $endpoint,
                $body,
                $this->getRequestSigningKey( $endpoint, $body ),
                home_url(),
                'UltraGuardPro/' . (defined('ULTRAGUARD_PRO_VERSION') ? ULTRAGUARD_PRO_VERSION : '1.0.0'),
                [
                    'timeout'   => $this->timeout,
                    'sslverify' => self::shouldVerifySslForUrl( $url ),
                ],
                $this->requireSignedResponses(),
                $this->getRequestIdentityKey( $endpoint, $body )
            );

            if ( ! $result['success'] ) {
                $error_body    = is_array( $result['body'] ?? null ) ? $result['body'] : [];
                $last_failure  = [
                    'success' => false,
                    'message' => (string) ( $error_body['message'] ?? $result['message'] ?? 'Request failed' ),
                    'code'    => $error_body['code'] ?? ( $result['code'] ?? 'request_failed' ),
                    'status'  => is_numeric( $result['code'] ?? null ) ? (int) $result['code'] : 0,
                    'body'    => $error_body,
                    'url'     => $url,
                ];

                continue;
            }

            $this->updateStats($this->current_operation, true);

            $body = is_array( $result['body'] ?? null ) ? $result['body'] : [ 'success' => false, 'message' => 'Invalid API response', 'code' => 'invalid_response' ];

            if ( is_array( $body ) ) {
                $this->persistRemoteState( $body );
            }

            return $body;
        }

        $this->updateStats($this->current_operation, false);

        return is_array( $last_failure ) ? $last_failure : [
            'success' => false,
            'message' => 'Request failed',
            'code'    => 'request_failed',
            'status'  => 0,
            'body'    => [],
        ];
    }

    /**
     * Register the current installation with the UltraGuard backend.
     *
     * @return self
     */
    private function registerInstallation(): self {
        $this->current_operation = 'site_register';

        $this->ensureCredentialsLoaded();

        $payload = $this->getRegistrationPayload();
        if ( empty( $payload['site_url'] ) ) {
            return $this;
        }

        $result = $this->callApi( self::ENDPOINT_SITE_REGISTER, $payload );

        if ( ! empty( $result['success'] ) ) {
            $this->handleSuccess( $result );
        } elseif ( ! empty( $result['message'] ) ) {
            $this->handleError( new \WP_Error( 'site_register_failed', (string) $result['message'] ) );
        }

        return $this;
    }

    /**
     * Send a message acknowledgement to the backend.
     *
     * @param string $message_key
     * @return bool
     */
    private function acknowledgeMessage( string $message_key ): bool {
        if ( '' === $message_key ) {
            return false;
        }

        $this->current_operation = 'message_ack';

        $result = $this->callApi(
            self::ENDPOINT_MESSAGES_ACK,
            array_merge(
                $this->getRegistrationPayload(),
                [
                    'message_key' => $message_key,
                ]
            )
        );

        return ! empty( $result['success'] );
    }

    /**
     * Make sure the local instance has the current saved credentials loaded.
     *
     * @return void
     */
    private function ensureCredentialsLoaded(): void {
        if ( ! function_exists( 'get_option' ) ) {
            return;
        }

        if ( '' === $this->license_key ) {
            $this->license_key = (string) get_option( self::OPTION_KEY, '' );
        }

        if ( '' === $this->license_email ) {
            $this->license_email = (string) get_option( self::OPTION_EMAIL, '' );
        }
    }

    /**
     * Revalidate the stored license against the remote service when a forced status read is requested.
     *
     * @return array|null
     */
    private function refreshRemoteStatus(): ?array {
        $this->ensureCredentialsLoaded();

        if ( '' === $this->license_key ) {
            return $this->buildStatusSnapshot();
        }

        $result = $this->callApi(
            self::ENDPOINT_VALIDATE,
            [
                'license_key' => $this->license_key,
                'email'       => $this->license_email,
                'site_url'    => home_url(),
            ]
        );

        if ( empty( $result['success'] ) ) {
            $this->maybeDowngradeToFree( $result, 'status_refresh' );
            return null;
        }

        return $this->buildStatusSnapshot();
    }

    /**
     * Build the current normalized status payload from local state.
     *
     * @return array
     */
    private function buildStatusSnapshot(): array {
        $cached_status = CacheManager::get_instance()->get( 'ug_license_status', 'asset' );
        $data          = get_option( self::OPTION_DATA, [] );
        $license_key   = '' !== $this->license_key ? $this->license_key : (string) get_option( self::OPTION_KEY, '' );
        $email         = '' !== $this->license_email ? $this->license_email : (string) get_option( self::OPTION_EMAIL, '' );

        if ( '' === trim( $license_key ) ) {
            $status          = 'free';
            $free_delay_days = isset( $data['free_delay_days'] ) ? (int) $data['free_delay_days'] : self::FREE_UPDATE_DELAY;
            $subscriber      = get_option( self::OPTION_SUBSCRIBER, [] );
            $messages        = get_option( self::OPTION_MESSAGES, [] );

            return [
                'status'       => $status,
                'plan'         => 'free',
                'is_pro'       => false,
                'is_valid'     => false,
                'license_key'  => '',
                'email'        => $email,
                'expires_at'   => '',
                'manage_url'   => '',
                'upgrade_url'  => '',
                'data'         => is_array( $data ) ? $data : [],
                'subscriber'   => is_array( $subscriber ) ? $subscriber : [],
                'messages'     => is_array( $messages ) ? $messages : [],
                'free_layout'  => is_array( $data['free_layout'] ?? null ) ? $data['free_layout'] : [],
                'update_delay' => max( 0, $free_delay_days ),
            ];
        }

        $status = strtolower( (string) ( $cached_status ?: ( $data['status'] ?? 'free' ) ) );
        $plan   = strtolower( (string) ( $data['plan'] ?? $data['plan_name'] ?? $data['tier'] ?? $data['license_type'] ?? '' ) );

        $valid_statuses = [ 'valid', 'lifetime', 'trial', 'active' ];
        $free_statuses  = [ 'free', 'invalid', 'inactive', 'expired', 'revoked', 'cancelled', 'canceled', 'suspended', 'unknown', '' ];
        $free_plans     = [ 'free', 'community', 'starter', 'basic' ];

        $data_is_pro = null;
        if ( isset( $data['is_pro'] ) ) {
            $data_is_pro = filter_var( $data['is_pro'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
        }

        $is_valid = in_array( $status, $valid_statuses, true );

        if ( null !== $data_is_pro ) {
            $is_pro = $data_is_pro && $is_valid;
        } elseif ( $is_valid ) {
            $is_pro = '' === $plan || ! in_array( $plan, $free_plans, true );
        } else {
            $is_pro = false;
        }

        if ( in_array( $status, $free_statuses, true ) ) {
            $is_pro = false;
        }

        $normalized_plan = $is_pro ? ( $plan ?: 'pro' ) : 'free';
        $expires_at      = (string) ( $data['expires_at'] ?? $data['expires'] ?? $data['expiry_date'] ?? $data['valid_until'] ?? '' );
        $manage_url      = (string) ( $data['billing_portal_url'] ?? $data['manage_url'] ?? $data['account_url'] ?? '' );
        $upgrade_url     = (string) ( $data['upgrade_url'] ?? '' );
        $free_delay_days = isset( $data['free_delay_days'] ) ? (int) $data['free_delay_days'] : self::FREE_UPDATE_DELAY;
        $subscriber      = get_option( self::OPTION_SUBSCRIBER, [] );
        $messages        = get_option( self::OPTION_MESSAGES, [] );

        return [
            'status'       => $status,
            'plan'         => $normalized_plan,
            'is_pro'       => $is_pro,
            'is_valid'     => $is_valid,
            'license_key'  => $this->maskKey( $license_key ),
            'email'        => $email,
            'expires_at'   => $expires_at,
            'manage_url'   => $manage_url,
            'upgrade_url'  => $upgrade_url,
            'data'         => is_array( $data ) ? $data : [],
            'subscriber'   => is_array( $subscriber ) ? $subscriber : [],
            'messages'     => is_array( $messages ) ? $messages : [],
            'free_layout'  => is_array( $data['free_layout'] ?? null ) ? $data['free_layout'] : [],
            'update_delay' => $is_pro ? 0 : max( 0, $free_delay_days ),
        ];
    }

    /**
     * Build the common site-registration payload sent to the backend.
     *
     * @return array
     */
    private function getRegistrationPayload(): array {
        $this->ensureCredentialsLoaded();

        return [
            'license_key'    => $this->license_key,
            'email'          => $this->license_email ?: (string) get_option( 'admin_email', '' ),
            'site_url'       => home_url(),
            'site_name'      => get_bloginfo( 'name' ),
            'wp_version'     => get_bloginfo( 'version' ),
            'php_version'    => PHP_VERSION,
            'plugin_version' => defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '1.0.0',
            'metadata'       => [
                'registered_via' => 'ultraguard_client',
                'is_multisite'   => is_multisite(),
            ],
        ];
    }

    /**
     * Decide which API base should be used for a given endpoint.
     *
     * @param string $endpoint
     * @return string
     */
    public static function serverBaseUrl(): string {
        return untrailingslashit( self::SERVER_BASE_URL );
    }

    public static function serverApiBaseUrl(): string {
        return self::serverBaseUrl() . '/wp-json/' . trim( self::SERVER_API_NAMESPACE, '/' );
    }

    public static function endpointUrlCandidates( string $endpoint ): array {
        $normalized_path = trim( self::SERVER_API_NAMESPACE . '/' . ltrim( $endpoint, '/' ), '/' );
        $base_url        = self::serverBaseUrl();
        $host            = strtolower( (string) wp_parse_url( $base_url, PHP_URL_HOST ) );
        $is_local_host   = (
            '' === $host
            || 'localhost' === $host
            || '127.0.0.1' === $host
            || '::1' === $host
            || str_ends_with( $host, '.test' )
            || str_ends_with( $host, '.local' )
            || str_ends_with( $host, '.localhost' )
        );

        $candidates = [
            self::serverApiBaseUrl() . '/' . ltrim( $endpoint, '/' ),
        ];

        if ( $is_local_host ) {
            $candidates[] = $base_url . '/?rest_route=/' . $normalized_path;
            $candidates[] = $base_url . '/index.php?rest_route=/' . $normalized_path;
        }

        return array_values(
            array_unique(
                $candidates
            )
        );
    }

    private static function shouldVerifySslForUrl( string $url ): bool {
        $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
        $default = true;

        if (
            '' === $host
            || 'localhost' === $host
            || '127.0.0.1' === $host
            || '::1' === $host
            || str_ends_with( $host, '.test' )
            || str_ends_with( $host, '.local' )
            || str_ends_with( $host, '.localhost' )
        ) {
            $default = false;
        }

        return (bool) apply_filters( 'ultraguard_pro/license_server_sslverify', $default, $url );
    }

    /**
     * Decide which signing key should be used for a given request.
     *
     * @param string $endpoint
     * @param array  $data
     * @return string
     */
    private function getRequestSigningKey( string $endpoint, array $data = [] ): string {
        $transport_key = (string) ( $data['transport_key'] ?? '' );
        if ( '' !== $transport_key ) {
            return $transport_key;
        }

        $identity_key = $this->getRequestIdentityKey( $endpoint, $data );
        $stored_transport = $this->getStoredTransportData();
        $stored_fingerprint = (string) ( $stored_transport['transport_identity_fingerprint'] ?? '' );
        if (
            '' !== (string) ( $stored_transport['transport_key'] ?? '' )
            && '' !== $identity_key
            && hash( 'sha256', $identity_key ) === $stored_fingerprint
        ) {
            return (string) $stored_transport['transport_key'];
        }

        $candidate = (string) ( $data['license_key'] ?? '' );
        if ( '' !== $candidate ) {
            return $candidate;
        }

        $this->ensureCredentialsLoaded();

        if ( '' !== $this->license_key ) {
            return $this->license_key;
        }

        if ( in_array( $endpoint, [ self::ENDPOINT_HEARTBEAT, self::ENDPOINT_SITE_REGISTER, self::ENDPOINT_MESSAGES, self::ENDPOINT_MESSAGES_ACK ], true ) ) {
            return 'ultraguard-public';
        }

        return 'ultraguard-public';
    }

    /**
     * Decide which identity key should be exposed in request headers.
     *
     * @param string $endpoint
     * @param array  $data
     * @return string
     */
    private function getRequestIdentityKey( string $endpoint, array $data = [] ): string {
        $candidate = (string) ( $data['license_key'] ?? '' );
        if ( '' !== $candidate ) {
            return $candidate;
        }

        $this->ensureCredentialsLoaded();

        if ( '' !== $this->license_key ) {
            return $this->license_key;
        }

        if ( in_array( $endpoint, [ self::ENDPOINT_HEARTBEAT, self::ENDPOINT_SITE_REGISTER, self::ENDPOINT_MESSAGES, self::ENDPOINT_MESSAGES_ACK ], true ) ) {
            return 'ultraguard-public';
        }

        return 'ultraguard-public';
    }

    /**
     * Persist remote license/profile/subscriber/message state locally.
     *
     * @param array $result
     * @return void
     */
    private function persistRemoteState( array $result ): void {
        if ( ! function_exists( 'get_option' ) ) {
            return;
        }

        $stored = get_option( self::OPTION_DATA, [] );
        if ( ! is_array( $stored ) ) {
            $stored = [];
        }

        $state_changed = false;

        $license_payload = [];
        if ( isset( $result['data'] ) && is_array( $result['data'] ) ) {
            $license_payload = array_merge( $license_payload, $result['data'] );
        }
        if ( isset( $result['license'] ) && is_array( $result['license'] ) ) {
            $license_payload = array_merge( $license_payload, $result['license'] );
        }
        if ( isset( $result['profile'] ) && is_array( $result['profile'] ) ) {
            $license_payload = array_merge( $license_payload, $result['profile'] );
        }
        if ( isset( $result['free_delay_days'] ) ) {
            $license_payload['free_delay_days'] = max( 0, (int) $result['free_delay_days'] );
        }
        if ( isset( $result['free_layout'] ) && is_array( $result['free_layout'] ) ) {
            $license_payload['free_layout'] = $result['free_layout'];
        }
        if ( isset( $result['transport'] ) && is_array( $result['transport'] ) ) {
            $transport = $result['transport'];

            if ( ! empty( $transport['key'] ) ) {
                $license_payload['transport_key'] = sanitize_text_field( (string) $transport['key'] );
            }
            if ( isset( $transport['mode'] ) ) {
                $license_payload['transport_mode'] = sanitize_key( (string) $transport['mode'] );
            }
            if ( isset( $transport['issued_at'] ) ) {
                $license_payload['transport_issued_at'] = sanitize_text_field( (string) $transport['issued_at'] );
            }
            if ( isset( $transport['site_url'] ) ) {
                $license_payload['transport_site_url'] = esc_url_raw( (string) $transport['site_url'] );
            }
            if ( isset( $transport['identity_fingerprint'] ) ) {
                $license_payload['transport_identity_fingerprint'] = sanitize_text_field( (string) $transport['identity_fingerprint'] );
            }
        }

        if ( ! empty( $license_payload ) ) {
            update_option( self::OPTION_DATA, array_merge( $stored, $license_payload ), false );
            $state_changed = true;
        }

        if ( isset( $result['subscriber'] ) && is_array( $result['subscriber'] ) ) {
            update_option( self::OPTION_SUBSCRIBER, $result['subscriber'], false );
            $state_changed = true;
        }

        if ( isset( $result['messages'] ) && is_array( $result['messages'] ) ) {
            update_option( self::OPTION_MESSAGES, $this->sanitizeMessages( $result['messages'] ), false );
            $state_changed = true;
        }

        if ( $state_changed ) {
            update_option( self::OPTION_LAST_SYNC, time(), false );
            $this->invalidateCachedStatus();
        }
    }

    /**
     * Determine whether the remote entitlement state should be refreshed.
     *
     * @return bool
     */
    private function isRemoteStatusRefreshDue(): bool {
        if ( ! function_exists( 'get_option' ) ) {
            return true;
        }

        $last_sync = (int) get_option( self::OPTION_LAST_SYNC, 0 );

        return $last_sync <= 0 || ( time() - $last_sync ) >= self::CACHE_LICENSE;
    }

    /**
     * Queue a background entitlement refresh if one is not already pending.
     *
     * @return void
     */
    private function scheduleStatusRefresh(): void {
        if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) {
            return;
        }

        if ( wp_next_scheduled( 'ultraguard_refresh_license_status' ) ) {
            return;
        }

        wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'ultraguard_refresh_license_status' );
    }

    /**
     * Decide whether the installation should attempt a background registration.
     *
     * @param bool $allow_cron_context Whether cron requests are allowed to trigger checks.
     * @return bool
     */
    private static function shouldAttemptInstallationRegistration( bool $allow_cron_context = false ): bool {
        if ( ! function_exists( 'get_option' ) || ! function_exists( 'get_transient' ) ) {
            return false;
        }

        if ( ! $allow_cron_context && ( ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) ) {
            return false;
        }

        if ( get_transient( self::TRANSIENT_REGISTER_RETRY ) ) {
            return false;
        }

        $subscriber = get_option( self::OPTION_SUBSCRIBER, [] );
        $last_seen  = strtotime( (string) ( $subscriber['last_seen_at'] ?? '' ) );
        $stale      = ! $last_seen || ( time() - $last_seen ) >= ( 12 * HOUR_IN_SECONDS );

        return empty( $subscriber ) || $stale;
    }

    /**
     * Ensure the recurring background registration retry cron exists.
     *
     * @return void
     */
    private static function ensureRegistrationRetryScheduled(): void {
        if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_event' ) ) {
            return;
        }

        if ( ! wp_next_scheduled( self::HOOK_REGISTER_INSTALLATION_RETRY ) ) {
            wp_schedule_event( time() + ( 15 * MINUTE_IN_SECONDS ), 'hourly', self::HOOK_REGISTER_INSTALLATION_RETRY );
        }
    }

    /**
     * Sanitize server-provided messages before storing them.
     *
     * @param array $messages
     * @return array
     */
    private function sanitizeMessages( array $messages ): array {
        $clean = [];

        foreach ( $messages as $message ) {
            if ( ! is_array( $message ) ) {
                continue;
            }

            $clean[] = [
                'message_key' => sanitize_key( (string) ( $message['message_key'] ?? '' ) ),
                'message_type' => sanitize_key( (string) ( $message['message_type'] ?? 'notification' ) ),
                'title'       => sanitize_text_field( (string) ( $message['title'] ?? '' ) ),
                'body'        => wp_kses_post( (string) ( $message['body'] ?? '' ) ),
                'cta_label'   => sanitize_text_field( (string) ( $message['cta_label'] ?? '' ) ),
                'cta_url'     => esc_url_raw( (string) ( $message['cta_url'] ?? '' ) ),
                'dismissible' => ! empty( $message['dismissible'] ),
                'payload'     => is_array( $message['payload'] ?? null ) ? $message['payload'] : [],
            ];
        }

        return $clean;
    }

    /**
     * Read the currently stored transport metadata, if any.
     *
     * @return array
     */
    private function getStoredTransportData(): array {
        $stored = get_option( self::OPTION_DATA, [] );

        return is_array( $stored ) ? $stored : [];
    }

    /**
     * Downgrade local state to free when the server authoritatively rejects the license.
     *
     * @param array  $result
     * @param string $operation
     * @return void
     */
    private function maybeDowngradeToFree( array $result, string $operation ): void {
        if ( ! $this->shouldDowngradeToFree( $result ) ) {
            return;
        }

        $this->persistFreeState( $operation );
        delete_option( self::OPTION_KEY );
        delete_option( self::OPTION_EMAIL );

        self::logInfo(
            'Downgraded local license state to free after authoritative server rejection.',
            [
                'operation' => $operation,
                'code'      => $result['code'] ?? '',
                'status'    => $result['status'] ?? '',
                'message'   => $result['message'] ?? '',
            ]
        );
    }

    /**
     * Determine whether a failed response should demote the local site to free.
     *
     * @param array $result
     * @return bool
     */
    private function shouldDowngradeToFree( array $result ): bool {
        $status_code = is_numeric( $result['status'] ?? null ) ? (int) $result['status'] : 0;
        $error_code  = sanitize_key( (string) ( $result['code'] ?? '' ) );
        $state       = strtolower( (string) ( $result['body']['status'] ?? $result['status_text'] ?? '' ) );
        $message     = strtolower( (string) ( $result['message'] ?? '' ) );

        if ( in_array( $status_code, [ 401, 403, 404 ], true ) ) {
            return true;
        }

        if ( in_array( $state, [ 'invalid', 'inactive', 'expired', 'revoked', 'blocked', 'suspended', 'cancelled', 'canceled', 'unregistered' ], true ) ) {
            return true;
        }

        if ( in_array( $error_code, [ 'invalid_license', 'license_inactive', 'site_not_activated', 'email_mismatch', 'rest_forbidden', 'forbidden', 'revoked', 'expired', 'blocked', 'suspended', 'unregistered' ], true ) ) {
            return true;
        }

        foreach ( [ 'expired', 'revoked', 'blocked', 'suspended', 'unregistered', 'not found', 'unauthorized', 'forbidden' ] as $needle ) {
            if ( str_contains( $message, $needle ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Persist a normalized local free-state payload.
     *
     * @param string $reason
     * @return void
     */
    private function persistFreeState( string $reason ): void {
        $stored = get_option( self::OPTION_DATA, [] );
        $stored = is_array( $stored ) ? $stored : [];

        $free_state = [
            'status'            => 'free',
            'plan'              => 'free',
            'plan_name'         => 'Free',
            'tier'              => 'free',
            'license_type'      => 'free',
            'is_pro'            => false,
            'is_valid'          => false,
            'free_delay_days'   => isset( $stored['free_delay_days'] ) ? max( 0, (int) $stored['free_delay_days'] ) : self::FREE_UPDATE_DELAY,
            'free_layout'       => is_array( $stored['free_layout'] ?? null ) ? $stored['free_layout'] : [],
            'downgraded_at'     => current_time( 'mysql', true ),
            'downgraded_reason' => sanitize_key( $reason ),
        ];

        update_option( self::OPTION_DATA, $free_state, false );
        CacheManager::get_instance()->set( 'ug_license_status', 'free', 'asset' );
        delete_option( self::OPTION_KEY );
        delete_option( self::OPTION_EMAIL );

        $this->license_key   = '';
        $this->license_email = '';
        $this->invalidateCachedStatus();
    }

    private function invalidateCachedStatus(): void {
        delete_transient( self::TRANSIENT_STATUS );
        unset( self::$cache['license_status'] );
        $this->last_result = [];
        $this->last_error  = null;
    }

    private function requireSignedResponses(): bool {
        return (bool) apply_filters( 'ultraguard_pro/license_info/require_signed_response', false );
    }

    // =========================================================================
    // HANDLERS
    // =========================================================================

    /**
     * Handle successful operation
     *
     * @param array $result
     * @return void
     */
    private function handleSuccess(array $result): void {
        $this->last_result = $result;
        $this->last_error = null;

        if ($this->success_callback) {
            call_user_func($this->success_callback, $result, $this);
        }

        if ($this->then_callback) {
            call_user_func($this->then_callback, $this);
        }
    }

    /**
     * Handle error
     *
     * @param \WP_Error $error
     * @return self
     */
    private function handleError(\WP_Error $error): self {
        $this->last_error = $error;
        $this->last_result = ['success' => false, 'message' => $error->get_error_message()];

        if ($this->error_callback) {
            call_user_func($this->error_callback, $error, $this);
        }

        if ($this->catch_callback) {
            call_user_func($this->catch_callback, $error, $this);
        }

        self::logError($error->get_error_message());

        return $this;
    }

    // =========================================================================
    // UTILITY METHODS
    // =========================================================================

    /**
     * Mask license key for display
     *
     * @param string $key
     * @return string
     */
    private function maskKey(string $key): string {
        if (empty($key)) {
            return '';
        }

        $length = strlen($key);
        if ($length <= 8) {
            return str_repeat('•', $length);
        }

        $visible = 4;
        $masked = str_repeat('•', $length - ($visible * 2));
        return substr($key, 0, $visible) . $masked . substr($key, -$visible);
    }

    /**
     * Log informational message
     *
     * @param string $message
     * @param array $context
     * @return void
     */
    private static function logInfo(string $message, array $context = []): void {
        if (self::$logger) {
            call_user_func(self::$logger, 'info', $message, $context);
        } elseif (defined('WP_DEBUG') && WP_DEBUG) {
            \UltraguardPro\core\Logger::error( '[UltraGuard License] ' . $message . ' ' . wp_json_encode($context) );
        }
    }

    /**
     * Log error message
     *
     * @param string $message
     * @param mixed $error
     * @return void
     */
    private static function logError(string $message, $error = null): void {
        $context = [];

        if ($error instanceof \WP_Error) {
            $context['code'] = $error->get_error_code();
            $context['message'] = $error->get_error_message();
        } elseif ($error instanceof \Exception) {
            $context['message'] = $error->getMessage();
            $context['file'] = $error->getFile();
            $context['line'] = $error->getLine();
        } elseif (is_array($error)) {
            $context = $error;
        }

        if (self::$logger) {
            call_user_func(self::$logger, 'error', $message, $context);
        } else {
            \UltraguardPro\core\Logger::error( '[UltraGuard License Error] ' . $message . ' ' . wp_json_encode($context) );
        }

        if (self::$error_handler) {
            call_user_func(self::$error_handler, $message, $context);
        }
    }

    // =========================================================================
    // MAGIC METHODS
    // =========================================================================

    /**
     * Prevent cloning
     */
    private function __clone() {}

    /**
     * Debug info
     *
     * @return array
     */
    public function __debugInfo(): array {
        return [
            'license_key' => $this->maskKey($this->license_key),
            'license_email' => $this->license_email,
            'is_pro' => $this->isPro(),
            'timeout' => $this->timeout,
            'async' => $this->async,
            'batch_mode' => $this->batch_mode,
            'last_result' => $this->last_result,
            'has_error' => $this->last_error !== null,
        ];
    }
}
