<?php
/**
 * FirewallAjax — ALL AJAX handlers for the Firewall module.
 *
 * Covers actions from every firewall subpage:
 *   Dashboard     : refresh_stats, get_logs, scan_threats, permaban_ip, export_settings
 *   Rules         : get/save/delete/toggle/enable/disable/clear/import/export, templates
 *   Logs          : export/clear/filter/delete logs, block/whitelist from log, log details
 *   Country       : save/refresh/block/unblock country, test, import/export, bulk add
 *   Add Rule      : save_new, test, draft, clear form, validate pattern, preview, template
 *   Settings      : save settings, test firewall, update rules, clear cache, activity
 *
 * @package UltraguardPro\modules\firewall\ajax
 */

namespace UltraguardPro\modules\firewall\ajax;

use UltraguardPro\Plugin;
use UltraguardPro\support\AjaxResponseBuilder;

if ( ! defined( 'ABSPATH' ) ) { exit; }

// phpcs:disable WordPress.Security.NonceVerification.Missing -- Firewall AJAX actions are registered through the shared protected AJAX manager.

class FirewallAjax {

    private Plugin $plugin;

    public function __construct( Plugin $plugin ) {
        $this->plugin = $plugin;
    }

    public function register(): void {
        $ajax    = $this->plugin->ajax;
        $actions = [
            // ── Legacy (keep existing action names) ──────────────────────────
            'ultraguard_firewall_get_rules'        => [ $this, 'handle_get_rules' ],
            'ultraguard_firewall_save_rule'         => [ $this, 'handle_save_rule' ],
            'ultraguard_firewall_delete_rule'       => [ $this, 'handle_delete_rule' ],
            'ultraguard_firewall_toggle_rule'       => [ $this, 'handle_toggle_rule' ],
            'ultraguard_firewall_get_logs'          => [ $this, 'handle_get_logs' ],
            'ultraguard_firewall_block_ip'          => [ $this, 'handle_block_ip' ],
            'ultraguard_firewall_unblock_ip'        => [ $this, 'handle_unblock_ip' ],
            'ultraguard_firewall_get_blacklist'     => [ $this, 'handle_get_blacklist' ],
            'ultraguard_firewall_get_whitelist'     => [ $this, 'handle_get_whitelist' ],
            'ultraguard_firewall_add_whitelist'     => [ $this, 'handle_add_whitelist' ],
            'ultraguard_firewall_remove_whitelist'  => [ $this, 'handle_remove_whitelist' ],
            'ultraguard_firewall_get_stats'         => [ $this, 'handle_get_stats' ],
            'ultraguard_firewall_import_ips'        => [ $this, 'handle_import_ips' ],
            'ultraguard_export_ips'                 => [ $this, 'handle_export_ips' ],
            'ultraguard_validate_ips'               => [ $this, 'handle_validate_ips' ],
            'ultraguard_bulk_block_ips'             => [ $this, 'handle_bulk_block_ips' ],
            'ultraguard_bulk_whitelist_ips'         => [ $this, 'handle_bulk_whitelist_ips' ],
            'ultraguard_scan_ip_logs'               => [ $this, 'handle_scan_ip_logs' ],
            'ultraguard_clear_expired_ips'          => [ $this, 'handle_clear_expired_ips' ],

            // ── Dashboard page ────────────────────────────────────────────────
            'ultraguard_refresh_firewall_stats'     => [ $this, 'handle_refresh_stats' ],
            'ultraguard_scan_for_threats'           => [ $this, 'handle_scan_threats' ],
            'ultraguard_permaban_ip'                => [ $this, 'handle_permaban_ip' ],
            'ultraguard_export_firewall_settings'   => [ $this, 'handle_export_settings' ],

            // ── Rules page ────────────────────────────────────────────────────
            'ultraguard_add_firewall_rule'          => [ $this, 'handle_add_firewall_rule' ],
            'ultraguard_edit_firewall_rule'         => [ $this, 'handle_edit_firewall_rule' ],
            'ultraguard_update_firewall_rule'       => [ $this, 'handle_update_firewall_rule' ],
            'ultraguard_delete_rules'               => [ $this, 'handle_delete_rules_bulk' ],
            'ultraguard_toggle_rule_status'         => [ $this, 'handle_toggle_rule_status' ],
            'ultraguard_enable_rules'               => [ $this, 'handle_enable_rules' ],
            'ultraguard_disable_rules'              => [ $this, 'handle_disable_rules' ],
            'ultraguard_clear_all_rules'            => [ $this, 'handle_clear_all_rules' ],
            'ultraguard_import_firewall_rules'      => [ $this, 'handle_import_rules' ],
            'ultraguard_export_firewall_rules'      => [ $this, 'handle_export_rules' ],
            'ultraguard_get_rule_details'           => [ $this, 'handle_get_rule_details' ],
            'ultraguard_add_template_rule'          => [ $this, 'handle_add_template_rule' ],
            'ultraguard_add_threat_protection'      => [ $this, 'handle_add_threat_protection' ],
            'ultraguard_add_wp_protection'          => [ $this, 'handle_add_wp_protection' ],
            'ultraguard_enable_rate_limiting'       => [ $this, 'handle_enable_rate_limiting' ],

            // ── Logs page ──────────────────────────────────────────────────────
            'ultraguard_export_firewall_logs'       => [ $this, 'handle_export_logs' ],
            'ultraguard_clear_firewall_logs'        => [ $this, 'handle_clear_logs' ],
            'ultraguard_get_firewall_logs_page'     => [ $this, 'handle_get_logs_page' ],
            'ultraguard_filter_firewall_logs'       => [ $this, 'handle_filter_logs' ],
            'ultraguard_delete_firewall_logs'       => [ $this, 'handle_delete_logs' ],
            'ultraguard_block_ip_from_log'          => [ $this, 'handle_block_ip_from_log' ],
            'ultraguard_whitelist_ip_from_log'      => [ $this, 'handle_whitelist_ip_from_log' ],
            'ultraguard_get_log_details'            => [ $this, 'handle_get_log_details' ],

            // ── Country blocking page ──────────────────────────────────────────
            'ultraguard_save_country_settings'      => [ $this, 'handle_save_country_settings' ],
            'ultraguard_refresh_country_stats'      => [ $this, 'handle_refresh_country_stats' ],
            'ultraguard_block_country'              => [ $this, 'handle_block_country' ],
            'ultraguard_unblock_country'            => [ $this, 'handle_unblock_country' ],
            'ultraguard_test_country_blocking'      => [ $this, 'handle_test_country_blocking' ],
            'ultraguard_import_country_list'        => [ $this, 'handle_import_country_list' ],
            'ultraguard_export_country_list'        => [ $this, 'handle_export_country_list' ],
            'ultraguard_bulk_add_countries'         => [ $this, 'handle_bulk_add_countries' ],
            'ultraguard_toggle_country_status'      => [ $this, 'handle_toggle_country_status' ],
            'ultraguard_get_blocked_countries'      => [ $this, 'handle_get_blocked_countries' ],

            // ── Add Rule page ──────────────────────────────────────────────────
            'ultraguard_save_add_new_rule'          => [ $this, 'handle_save_add_new_rule' ],
            'ultraguard_test_firewall_rule'         => [ $this, 'handle_test_firewall_rule' ],
            'ultraguard_save_rule_draft'            => [ $this, 'handle_save_rule_draft' ],
            'ultraguard_clear_rule_form'            => [ $this, 'handle_clear_rule_form' ],
            'ultraguard_validate_rule_pattern'      => [ $this, 'handle_validate_rule_pattern' ],
            'ultraguard_preview_rule_effect'        => [ $this, 'handle_preview_rule_effect' ],
            'ultraguard_use_template'               => [ $this, 'handle_use_template' ],

            // ── Settings page ──────────────────────────────────────────────────
            'ultraguard_save_firewall_settings'     => [ $this, 'handle_save_firewall_settings' ],
            'ultraguard_test_firewall'              => [ $this, 'handle_test_firewall' ],
            'ultraguard_update_firewall_rules'      => [ $this, 'handle_update_firewall_data' ],
            'ultraguard_update_firewall_data'       => [ $this, 'handle_update_firewall_data' ],
            'ultraguard_clear_firewall_cache'       => [ $this, 'handle_clear_firewall_cache' ],
            'ultraguard_refresh_activity'           => [ $this, 'handle_refresh_activity' ],
            // Row-level record mutations (table action buttons)
            'ultraguard_firewall_edit_rule'         => [ $this, 'handle_edit_rule' ],
            'ultraguard_firewall_delete_log'        => [ $this, 'handle_delete_log' ],
            'ultraguard_firewall_edit_blacklist'    => [ $this, 'handle_edit_blacklist' ],
            'ultraguard_firewall_delete_blacklist_ip' => [ $this, 'handle_delete_blacklist_ip' ],
            'ultraguard_firewall_edit_whitelist'    => [ $this, 'handle_edit_whitelist' ],
            'ultraguard_download_export'            => [ $this, 'handle_download_export' ],
            // ── Realtime fallback polling ─────────────────────────────────────
            'ultraguard_rt_firewall_status'         => [ $this, 'handle_rt_firewall_status' ],
        ];

        foreach ( $actions as $action => $cb ) {
            $ajax->register_action( $action, $cb );
        }
    }

    // =========================================================================
    // LEGACY HANDLERS (existing action names preserved)
    // =========================================================================

    public function handle_get_rules(): void {
        $this->cap();
        $page   = max( 1, absint( $_POST['page'] ?? 1 ) );
        $action = sanitize_key( wp_unslash( $_POST['filter_action'] ?? ''  ) );
        $conds  = $action ? [ 'action' => $action ] : [];
        AjaxResponseBuilder::make()->success()->mergeData( (array) $this->t( 'firewall_rules' )->paginate( $page, 50, $conds, 'priority', 'ASC' ) )->send();
    }

    public function handle_save_rule(): void {
        $this->cap();
        $table = $this->t( 'firewall_rules' );
        $id    = absint( $_POST['rule_id'] ?? 0 );
        $data  = $this->rule_fields();
        if ( $id ) {
            $result = $table->update( $id, $data ) ? $id : false;
        } else {
            $data['created_by'] = get_current_user_id();
            $result             = $table->insert( $data );
        }
        if ( $result ) {
            do_action( 'ultraguard_firewall_rule_saved', $result );
            AjaxResponseBuilder::make()
			->success( __( 'Rule saved', 'ultraguard-security-suite' ) )
			->addData( 'id', $result )
			->addNotification( __( 'Rule saved', 'ultraguard-security-suite' ), 'success' )
			->send();
        } else {
            AjaxResponseBuilder::make()
			->error( __( 'Failed to save rule', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to save rule', 'ultraguard-security-suite' ), 'error' )
			->send();
        }
    }

    public function handle_delete_rule(): void {
        $this->cap();
        $id = absint( $_POST['rule_id'] ?? 0 );
        if ( $this->t( 'firewall_rules' )->delete( $id ) ) {
            do_action( 'ultraguard_firewall_rule_saved' );
            AjaxResponseBuilder::make()
			->success( __( 'Rule deleted', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule deleted', 'ultraguard-security-suite' ), 'success' )
			->send();
        } else {
            AjaxResponseBuilder::make()
			->error( __( 'Failed to delete rule', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to delete rule', 'ultraguard-security-suite' ), 'error' )
			->send();
        }
    }

    public function handle_toggle_rule(): void {
        $this->cap();
        $id    = absint( $_POST['rule_id'] ?? 0 );
        $table = $this->t( 'firewall_rules' );
        $rule  = $table->get( $id );
        if ( empty( $rule['id'] ) ) { AjaxResponseBuilder::make()
			->error( __( 'Rule not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule not found', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $new = $rule['is_active'] ? 0 : 1;
        $table->update( $id, [ 'is_active' => $new ] );
        do_action( 'ultraguard_firewall_rule_saved' );
        AjaxResponseBuilder::make()->success()->addData( 'is_active', $new )->send();
    }

    public function handle_get_logs(): void {
        $this->cap();
        $page   = max( 1, absint( $_POST['page'] ?? 1 ) );
        $conds  = [];
        if ( $a  = sanitize_key( wp_unslash( $_POST['filter_action'] ?? ''  ) ) )       { $conds['action']     = $a; }
        if ( $ip = sanitize_text_field( wp_unslash( $_POST['filter_ip'] ?? '' ) ) )    { $conds['ip_address'] = $ip; }
        AjaxResponseBuilder::make()->success()->mergeData( (array) $this->t( 'firewall_logs' )->paginate( $page, 100, $conds, 'created_at', 'DESC' ) )->send();
    }

    public function handle_block_ip(): void {
        $this->cap();
        $ip       = sanitize_text_field( wp_unslash( $_POST['ip_address'] ?? '' ) );
        $reason   = sanitize_text_field( wp_unslash( $_POST['reason']     ?? 'manual_block' ) );
        $severity = sanitize_key( wp_unslash( $_POST['severity']          ?? 'medium'  ) );
        $expires  = sanitize_text_field( wp_unslash( $_POST['expires_at'] ?? '' ) ) ?: null;
        if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid IP address', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid IP address', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $id = $this->t( 'ip_blacklist' )->block_ip( $ip, $reason, 'manual', $severity, $expires );
        if ( $id ) { do_action( 'ultraguard_ip_list_updated' ); AjaxResponseBuilder::make()
			->success( __( 'IP blocked', 'ultraguard-security-suite' ) )
			->addData( 'id', $id )
			->addNotification( __( 'IP blocked', 'ultraguard-security-suite' ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed to block IP', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to block IP', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_unblock_ip(): void {
        $this->cap();
        $ip = sanitize_text_field( wp_unslash( $_POST['ip_address'] ?? '' ) );
        $id = absint( $_POST['blacklist_id'] ?? 0 );
        if ( $this->t( 'ip_blacklist' )->unblock_ip( $ip ) ) {
            do_action( 'ultraguard_ip_list_updated' );
            AjaxResponseBuilder::make()
			->success( __( 'IP unblocked', 'ultraguard-security-suite' ) )
			->addRemove( '#ug-fw-blacklist-row-' . $id, true, 300 )
			->addNotification( __( 'IP unblocked', 'ultraguard-security-suite' ), 'success' )
			->send();
        } else { AjaxResponseBuilder::make()
			->error( __( 'Failed', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_get_blacklist(): void {
        $this->cap();
        AjaxResponseBuilder::make()->success()->send();
    }

    public function handle_get_whitelist(): void {
        $this->cap();
        AjaxResponseBuilder::make()->success()->send();
    }

    public function handle_add_whitelist(): void {
        $this->cap();
        $id = $this->t( 'ip_whitelist' )->add_ip( sanitize_text_field( wp_unslash( $_POST['ip_address'] ?? '' ) ), sanitize_text_field( wp_unslash( $_POST['label'] ?? '' ) ) );
        if ( $id ) { do_action( 'ultraguard_ip_list_updated' ); AjaxResponseBuilder::make()
			->success( __( 'IP whitelisted', 'ultraguard-security-suite' ) )
			->addData( 'id', $id )
			->addNotification( __( 'IP whitelisted', 'ultraguard-security-suite' ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'IP already exists or invalid', 'ultraguard-security-suite' ) )
			->addNotification( __( 'IP already exists or invalid', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_remove_whitelist(): void {
        $this->cap();
        $id = absint( $_POST['whitelist_id'] ?? 0 );
        if ( $this->t( 'ip_whitelist' )->delete( $id ) ) {
            do_action( 'ultraguard_ip_list_updated' );
            AjaxResponseBuilder::make()
			->success( __( 'IP removed from whitelist', 'ultraguard-security-suite' ) )
			->addRemove( '#ug-fw-whitelist-row-' . $id, true, 300 )
			->addNotification( __( 'IP removed from whitelist', 'ultraguard-security-suite' ), 'success' )
			->send();
        } else { AjaxResponseBuilder::make()
			->error( __( 'Failed', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_import_ips(): void {
        $this->cap();

        $target = sanitize_key( wp_unslash( $_POST['list_target'] ?? 'blocked'  ) );
        $raw    = sanitize_textarea_field( wp_unslash( $_POST['ip_list'] ?? '' ) );
        $items  = $this->parse_ip_entries( $raw );

        if ( empty( $items ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'No IP addresses provided.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Paste one or more IPs to import.', 'ultraguard-security-suite' ), 'warning' )
                ->send();
            return;
        }

        if ( $target === 'allowed' ) {
            $label   = sanitize_text_field( wp_unslash( $_POST['whitelist_label'] ?? '' ) );
            $added   = $this->import_whitelist_entries( $items, $label );
            /* translators: %d: number */
            $message = sprintf( _n( '%d IP imported to whitelist.', '%d IPs imported to whitelist.', $added, 'ultraguard-security-suite' ), $added );
        } else {
            $reason   = sanitize_text_field( wp_unslash( $_POST['bulk_reason'] ?? __( 'Bulk import', 'ultraguard-security-suite' ) ) );
            $severity = sanitize_key( wp_unslash( $_POST['bulk_severity'] ?? 'medium'  ) );
            $added    = $this->import_blacklist_entries( $items, $reason, $severity, null );
            /* translators: %d: number */
            $message  = sprintf( _n( '%d IP imported to blacklist.', '%d IPs imported to blacklist.', $added, 'ultraguard-security-suite' ), $added );
        }

        do_action( 'ultraguard_ip_list_updated' );

        AjaxResponseBuilder::make()
            ->success( $message )
            ->addData( 'imported', $added )
            ->closeModal( 'fw-import-ips' )
            ->addReload( 700 )
            ->addNotification( $message, 'success' )
            ->send();
    }

    public function handle_validate_ips(): void {
        $this->cap();

        $raw   = sanitize_textarea_field( wp_unslash( $_POST['ip_list'] ?? $_POST['whitelist_ip_list'] ?? '' ) );
        $items = $this->parse_ip_entries( $raw );

        if ( empty( $items ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'No IP addresses provided.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Paste one or more IPs to validate.', 'ultraguard-security-suite' ), 'warning' )
                ->send();
            return;
        }

        $valid   = 0;
        $invalid = [];

        foreach ( $items as $item ) {
            if ( $this->normalize_ip_entry( $item ) ) {
                $valid++;
            } else {
                $invalid[] = $item;
            }
        }

        $message = empty( $invalid )
            /* translators: %d: number */
            ? sprintf( _n( '%d valid IP entry found.', '%d valid IP entries found.', $valid, 'ultraguard-security-suite' ), $valid )
            : sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( '%1$d valid, %2$d invalid IP entries found.', 'ultraguard-security-suite' ),
                $valid,
                count( $invalid )
            );

        AjaxResponseBuilder::make()
            ->success( $message )
            ->addData( 'valid', $valid )
            ->addData( 'invalid', count( $invalid ) )
            ->addData( 'invalid_entries', $invalid )
            ->addNotification( $message, empty( $invalid ) ? 'success' : 'warning' )
            ->send();
    }

    public function handle_bulk_block_ips(): void {
        $this->cap();

        $raw      = sanitize_textarea_field( wp_unslash( $_POST['ip_list'] ?? '' ) );
        $items    = $this->parse_ip_entries( $raw );
        $reason   = sanitize_text_field( wp_unslash( $_POST['bulk_reason'] ?? __( 'Bulk import', 'ultraguard-security-suite' ) ) );
        $severity = sanitize_key( wp_unslash( $_POST['bulk_severity'] ?? 'medium'  ) );

        if ( empty( $items ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'No IP addresses provided.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Paste one or more IPs to block.', 'ultraguard-security-suite' ), 'warning' )
                ->send();
            return;
        }

        $added   = $this->import_blacklist_entries( $items, $reason, $severity, null );
        /* translators: %d: number */
        $message = sprintf( _n( '%d IP blocked.', '%d IPs blocked.', $added, 'ultraguard-security-suite' ), $added );

        do_action( 'ultraguard_ip_list_updated' );

        AjaxResponseBuilder::make()
            ->success( $message )
            ->addData( 'blocked', $added )
            ->addReload( 700 )
            ->addNotification( $message, 'success' )
            ->send();
    }

    public function handle_bulk_whitelist_ips(): void {
        $this->cap();

        $raw     = sanitize_textarea_field( wp_unslash( $_POST['whitelist_ip_list'] ?? '' ) );
        $items   = $this->parse_ip_entries( $raw );
        $label   = sanitize_text_field( wp_unslash( $_POST['whitelist_label'] ?? '' ) );

        if ( empty( $items ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'No IP addresses provided.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Paste one or more IPs to whitelist.', 'ultraguard-security-suite' ), 'warning' )
                ->send();
            return;
        }

        $added   = $this->import_whitelist_entries( $items, $label );
        /* translators: %d: number */
        $message = sprintf( _n( '%d IP whitelisted.', '%d IPs whitelisted.', $added, 'ultraguard-security-suite' ), $added );

        do_action( 'ultraguard_ip_list_updated' );

        AjaxResponseBuilder::make()
            ->success( $message )
            ->addData( 'whitelisted', $added )
            ->addReload( 700 )
            ->addNotification( $message, 'success' )
            ->send();
    }

    public function handle_export_ips(): void {
        $this->cap();

        $list = sanitize_key( wp_unslash( $_POST['list'] ?? 'all'  ) );
        $dir  = wp_upload_dir();
        $path = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $path );

        if ( $list === 'blacklist' ) {
            $payload = $this->t( 'ip_blacklist' )->get_by( [ 'is_active' => 1 ], 10000, 'created_at', 'DESC' );
            $file    = 'firewall-blacklist-' . gmdate( 'Y-m-d' ) . '.json';
        } elseif ( $list === 'whitelist' ) {
            $payload = $this->t( 'ip_whitelist' )->get_by( [], 10000, 'created_at', 'DESC' );
            $file    = 'firewall-whitelist-' . gmdate( 'Y-m-d' ) . '.json';
        } else {
            $payload = [
                'blacklist' => $this->t( 'ip_blacklist' )->get_by( [ 'is_active' => 1 ], 10000, 'created_at', 'DESC' ),
                'whitelist' => $this->t( 'ip_whitelist' )->get_by( [], 10000, 'created_at', 'DESC' ),
            ];
            $file = 'firewall-ip-lists-' . gmdate( 'Y-m-d' ) . '.json';
        }

        file_put_contents( $path . $file, wp_json_encode( $payload, JSON_PRETTY_PRINT ) );

        $download_url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;

        AjaxResponseBuilder::make()
            ->success( __( 'IP list exported.', 'ultraguard-security-suite' ) )
            ->addData( 'download_url', $download_url )
            ->download( $download_url, $file )
            ->addNotification( __( 'IP list exported.', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    public function handle_scan_ip_logs(): void {
        $this->cap();

        $lookup = sanitize_text_field( wp_unslash( $_POST['lookup_ip'] ?? '' ) );
        $entry  = $this->normalize_ip_entry( $lookup );

        if ( ! $entry ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Enter a valid IP address or CIDR range.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Enter a valid IP address or CIDR range.', 'ultraguard-security-suite' ), 'error' )
                ->send();
            return;
        }

        $ip        = $entry['ip_address'];
        $blocked   = $this->t( 'ip_blacklist' )->get_by_field( 'ip_address', $ip );
        $allowed   = $this->t( 'ip_whitelist' )->get_by_field( 'ip_address', $ip );
        $geo       = $this->t( 'geolocation_data' )->get_by_field( 'ip_address', $ip );
        $status    = $blocked ? __( 'Blacklisted', 'ultraguard-security-suite' ) : ( $allowed ? __( 'Whitelisted', 'ultraguard-security-suite' ) : __( 'Not listed', 'ultraguard-security-suite' ) );
        $country   = $geo['country_code'] ?? __( 'Unknown', 'ultraguard-security-suite' );
        $threat    = isset( $geo['threat_score'] ) ? (int) $geo['threat_score'] : 0;
        /* translators: %1$s: string, %2$s: string, %3$s: string, %4$d: integer */
        $message   = sprintf( __( '%1$s: %2$s, country %3$s, threat score %4$d/100.', 'ultraguard-security-suite' ), $ip, $status, $country, $threat );

        AjaxResponseBuilder::make()
            ->success( $message )
            ->addData( 'ip_address', $ip )
            ->addData( 'status', $status )
            ->addData( 'country_code', $country )
            ->addData( 'threat_score', $threat )
            ->addNotification( $message, $blocked ? 'warning' : 'info' )
            ->send();
    }

    public function handle_clear_expired_ips(): void {
        $this->cap();

        $rows    = $this->t( 'ip_blacklist' )->get_by( [ 'is_active' => 1 ], 10000, 'created_at', 'DESC' );
        $expired = 0;
        $now     = current_time( 'timestamp', true );

        foreach ( $rows as $row ) {
            if ( empty( $row['expires_at'] ) ) {
                continue;
            }

            $stamp = strtotime( (string) $row['expires_at'] . ' UTC' );
            if ( $stamp && $stamp < $now && $this->t( 'ip_blacklist' )->update( (int) $row['id'], [ 'is_active' => 0 ] ) ) {
                $expired++;
            }
        }

        do_action( 'ultraguard_ip_list_updated' );

        AjaxResponseBuilder::make()
            /* translators: %d: number */
            ->success( sprintf( _n( '%d expired record cleared.', '%d expired records cleared.', $expired, 'ultraguard-security-suite' ), $expired ) )
            ->addData( 'cleared', $expired )
            ->addReload( 700 )
            /* translators: %d: number */
            ->addNotification( sprintf( _n( '%d expired record cleared.', '%d expired records cleared.', $expired, 'ultraguard-security-suite' ), $expired ), 'success' )
            ->send();
    }

    public function handle_get_stats(): void {
        $this->cap();
        $blocked = $this->t( 'blocked_requests' );
        $logs    = $this->t( 'firewall_logs' );
        $bl      = $this->t( 'ip_blacklist' );
        AjaxResponseBuilder::make()
			->success()
			->addData( 'total_blocked_today', $blocked->count( [ 'created_at >=' => gmdate( 'Y-m-d 00:00:00' ) ] ) )
			->addData( 'total_blocked_24h', $blocked->count( [ 'created_at >=' => gmdate( 'Y-m-d H:i:s', strtotime( '-24 hours' ) ) ] ) )
			->addData( 'active_blacklist', $bl->count( [ 'is_active' => 1 ] ) )
			->addData( 'top_blocked_ips', $blocked->get_top_blocked_ips( 5 ) )
			->addData( 'requests_chart', $logs->get_chart_data( 'created_at', 'id', 'hour', [], 24 ) )
			->addData( 'action_breakdown', $logs->get_pie_chart_data( 'action', 'id', 'COUNT', [], 5 ) )
			->send();
    }

    // =========================================================================
    // DASHBOARD PAGE
    // =========================================================================

    public function handle_refresh_stats(): void {
        $this->cap();
        $today = gmdate( 'Y-m-d 00:00:00' );
        AjaxResponseBuilder::make()
			->success( __( 'Stats refreshed', 'ultraguard-security-suite' ) )
			->addData( 'blocked_today', ( $t = $this->t( 'blocked_requests' ) ) ? $t->count( [ 'created_at >=' => $today ] ) : 0 )
			->addData( 'requests_today', ( $t = $this->t( 'firewall_logs' ) )    ? $t->count( [ 'created_at >=' => $today ] )  : 0 )
			->addData( 'active_rules', ( $t = $this->t( 'firewall_rules' ) )   ? $t->count( [ 'is_active' => 1 ] )           : 0 )
			->addData( 'blacklisted_ips', ( $t = $this->t( 'ip_blacklist' ) )     ? $t->count( [ 'is_active' => 1 ] )           : 0 )
			->addNotification( __( 'Stats refreshed', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_scan_threats(): void {
        $this->cap();
        do_action( 'ultraguard_firewall_threat_scan' );
        AjaxResponseBuilder::make()
			->success( __( 'Threat scan initiated', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Threat scan initiated', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_permaban_ip(): void {
        $this->cap();
        $ip = sanitize_text_field( wp_unslash( $_POST['ip_address'] ?? '' ) );
        if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid IP', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid IP', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $id = $this->t( 'ip_blacklist' )->block_ip( $ip, 'permanent_ban', 'manual', 'critical', null );
        if ( $id ) { AjaxResponseBuilder::make()
			/* translators: %s: string */
			->success( sprintf( __( 'IP %s permanently banned', 'ultraguard-security-suite' ), $ip ) )
			/* translators: %s: string */
			->addNotification( sprintf( __( 'IP %s permanently banned', 'ultraguard-security-suite' ), $ip ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed to ban IP', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to ban IP', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_export_settings(): void {
        $this->cap();
        $settings = \UltraguardPro\ultraguard_pro()->get_option( 'firewall_settings', [] );
        $dir      = wp_upload_dir();
        $path     = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $path );
        $file = 'firewall-settings-' . gmdate( 'Y-m-d' ) . '.json';
        file_put_contents( $path . $file, wp_json_encode( $settings, JSON_PRETTY_PRINT ) );
        $download_url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
        AjaxResponseBuilder::make()
			->success( __( 'Settings exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $download_url )
			->download( $download_url, $file )
			->addNotification( __( 'Settings exported', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    // =========================================================================
    // RULES PAGE
    // =========================================================================

    public function handle_add_firewall_rule(): void {
        $this->cap();
        $data               = $this->rule_fields();
        $data['created_by'] = get_current_user_id();
        $id = $this->t( 'firewall_rules' )->insert( $data );
        if ( $id ) { do_action( 'ultraguard_firewall_rule_saved', $id ); AjaxResponseBuilder::make()
			->success( __( 'Rule added', 'ultraguard-security-suite' ) )
			->addData( 'id', $id )
			->addNotification( __( 'Rule added', 'ultraguard-security-suite' ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed to add rule', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to add rule', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_edit_firewall_rule(): void {
        $this->cap();
        $rule = $this->t( 'firewall_rules' )->get( absint( $_POST['rule_id'] ?? 0 ) );
        if ( ! $rule ) { AjaxResponseBuilder::make()
			->error( __( 'Rule not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule not found', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        AjaxResponseBuilder::make()
			->success()
			->mergeData( (array) $rule )
			->send();
    }

    public function handle_update_firewall_rule(): void {
        $this->cap();
        $id = absint( $_POST['rule_id'] ?? 0 );
        if ( $this->t( 'firewall_rules' )->update( $id, $this->rule_fields() ) ) {
            do_action( 'ultraguard_firewall_rule_saved', $id );
            AjaxResponseBuilder::make()
			->success( __( 'Rule updated', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule updated', 'ultraguard-security-suite' ), 'success' )
			->send();
        } else { AjaxResponseBuilder::make()
			->error( __( 'Failed to update rule', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to update rule', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_delete_rules_bulk(): void {
        $this->cap();
        $ids   = array_map( 'absint', (array) wp_unslash( $_POST['rule_ids'] ?? [] ) );
        $count = 0;
        foreach ( $ids as $id ) { if ( $this->t( 'firewall_rules' )->delete( $id ) ) { $count++; } }
        do_action( 'ultraguard_firewall_rule_saved' );
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d rule deleted', '%d rules deleted', $count, 'ultraguard-security-suite' ), $count ) )
			->addData( 'deleted', $count )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d rule deleted', '%d rules deleted', $count, 'ultraguard-security-suite' ), $count ), 'success' )
			->send();
    }

    public function handle_toggle_rule_status(): void {
        $this->cap();
        $id    = absint( $_POST['rule_id'] ?? 0 );
        $table = $this->t( 'firewall_rules' );
        $rule  = $table->get( $id );
        if ( ! $rule ) { AjaxResponseBuilder::make()
			->error( __( 'Rule not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule not found', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $new = $rule['is_active'] ? 0 : 1;
        $table->update( $id, [ 'is_active' => $new ] );
        AjaxResponseBuilder::make()
			->success( $new ? __( 'Rule enabled', 'ultraguard-security-suite' ) : __( 'Rule disabled', 'ultraguard-security-suite' ) )
			->addData( 'is_active', $new )
			->addNotification( $new ? __( 'Rule enabled', 'ultraguard-security-suite' ) : __( 'Rule disabled', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_enable_rules(): void {
        $this->cap();
        $ids = array_map( 'absint', (array) wp_unslash( $_POST['rule_ids'] ?? [] ) );
        foreach ( $ids as $id ) { $this->t( 'firewall_rules' )->update( $id, [ 'is_active' => 1 ] ); }
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d rule enabled', '%d rules enabled', count( $ids ), 'ultraguard-security-suite' ), count( $ids ) ) )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d rule enabled', '%d rules enabled', count( $ids ), 'ultraguard-security-suite' ), count( $ids ) ), 'success' )
			->send();
    }

    public function handle_disable_rules(): void {
        $this->cap();
        $ids = array_map( 'absint', (array) wp_unslash( $_POST['rule_ids'] ?? [] ) );
        foreach ( $ids as $id ) { $this->t( 'firewall_rules' )->update( $id, [ 'is_active' => 0 ] ); }
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d rule disabled', '%d rules disabled', count( $ids ), 'ultraguard-security-suite' ), count( $ids ) ) )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d rule disabled', '%d rules disabled', count( $ids ), 'ultraguard-security-suite' ), count( $ids ) ), 'success' )
			->send();
    }

    public function handle_clear_all_rules(): void {
        $this->cap();
        $this->t( 'firewall_rules' )->truncate();
        do_action( 'ultraguard_firewall_rule_saved' );
        AjaxResponseBuilder::make()
			->success( __( 'All rules cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( 'All rules cleared', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_import_rules(): void {
        $this->cap();
        $json  = sanitize_textarea_field( wp_unslash( $_POST['rules_json'] ?? '' ) );
        $rules = json_decode( $json, true );
        if ( ! is_array( $rules ) ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid JSON', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid JSON', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $table    = $this->t( 'firewall_rules' );
        $imported = 0;
        foreach ( $rules as $rule ) {
            $data               = $this->sanitize_rule_array( $rule );
            $data['created_by'] = get_current_user_id();
            if ( $table->insert( $data ) ) { $imported++; }
        }
        do_action( 'ultraguard_firewall_rule_saved' );
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d rule imported', '%d rules imported', $imported, 'ultraguard-security-suite' ), $imported ) )
			->addData( 'imported', $imported )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d rule imported', '%d rules imported', $imported, 'ultraguard-security-suite' ), $imported ), 'success' )
			->send();
    }

    public function handle_export_rules(): void {
        $this->cap();
        $rules = $this->t( 'firewall_rules' )->get_by( [], 10000, 'priority', 'ASC' );
        $dir   = wp_upload_dir();
        $path  = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $path );
        $file  = 'firewall-rules-' . gmdate( 'Y-m-d' ) . '.json';
        file_put_contents( $path . $file, wp_json_encode( $rules, JSON_PRETTY_PRINT ) );
        $download_url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
        AjaxResponseBuilder::make()
			->success( __( 'Rules exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $download_url )
			->download( $download_url, $file )
			->addNotification( __( 'Rules exported', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_get_rule_details(): void {
        $this->cap();
        $rule = $this->t( 'firewall_rules' )->get( absint( $_POST['rule_id'] ?? 0 ) );
        if ( ! $rule ) { AjaxResponseBuilder::make()
			->error( __( 'Rule not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rule not found', 'ultraguard-security-suite' ), 'error' )
			->send( 404); return; }
        AjaxResponseBuilder::make()
			->success()
			->mergeData( (array) $rule )
			->send();
    }

    public function handle_add_template_rule(): void {
        $this->cap();
        $rule = $this->get_template_rule( sanitize_key( wp_unslash( $_POST['template'] ?? ''  ) ) );
        if ( ! $rule ) { AjaxResponseBuilder::make()
			->error( __( 'Unknown template', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Unknown template', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $rule['created_by'] = get_current_user_id();
        $id = $this->t( 'firewall_rules' )->insert( $rule );
        if ( $id ) { AjaxResponseBuilder::make()
			->success( __( 'Template rule added', 'ultraguard-security-suite' ) )
			->addData( 'id', $id )
			->addNotification( __( 'Template rule added', 'ultraguard-security-suite' ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_add_threat_protection(): void {
        $this->cap();
        $table = $this->t( 'firewall_rules' );
        $added = 0;
        foreach ( $this->threat_protection_rules() as $rule ) {
            $rule['created_by'] = get_current_user_id();
            if ( $table->insert( $rule ) ) { $added++; }
        }
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d threat rule added', '%d threat rules added', $added, 'ultraguard-security-suite' ), $added ) )
			->addData( 'added', $added )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d threat rule added', '%d threat rules added', $added, 'ultraguard-security-suite' ), $added ), 'success' )
			->send();
    }

    public function handle_add_wp_protection(): void {
        $this->cap();
        $table = $this->t( 'firewall_rules' );
        $added = 0;
        foreach ( $this->wp_protection_rules() as $rule ) {
            $rule['created_by'] = get_current_user_id();
            if ( $table->insert( $rule ) ) { $added++; }
        }
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d WP protection rule added', '%d WP protection rules added', $added, 'ultraguard-security-suite' ), $added ) )
			->addData( 'added', $added )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d WP protection rule added', '%d WP protection rules added', $added, 'ultraguard-security-suite' ), $added ), 'success' )
			->send();
    }

    public function handle_enable_rate_limiting(): void {
        $this->cap();
        update_option( 'ultraguard_rate_limiting_enabled', 1 );
        update_option( 'ultraguard_rate_limit_max_requests', absint( $_POST['max_requests']   ?? 200 ) );
        update_option( 'ultraguard_rate_limit_window',       absint( $_POST['window_seconds'] ?? 60  ) );
        AjaxResponseBuilder::make()
			->success( __( 'Rate limiting enabled', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Rate limiting enabled', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    // =========================================================================
    // LOGS PAGE
    // =========================================================================

    public function handle_export_logs(): void {
        $this->cap();
        $csv  = $this->t( 'firewall_logs' )->export_to_csv( [], [ 'ip_address','request_method','request_uri','action','threat_level','country_code','created_at' ], 100000 );
        $dir  = wp_upload_dir();
        $path = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $path );
        $file = 'firewall-logs-' . gmdate( 'Y-m-d' ) . '.csv';
        file_put_contents( $path . $file, $csv );
        $download_url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
        AjaxResponseBuilder::make()
			->success( __( 'Logs exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $download_url )
			->download( $download_url, $file )
			->addNotification( __( 'Logs exported', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_clear_logs(): void {
        $this->cap();
        $this->t( 'firewall_logs' )->truncate();
        AjaxResponseBuilder::make()
			->success( __( 'Firewall logs cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Firewall logs cleared', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_get_logs_page(): void {
        $this->cap();
        $page  = max( 1, absint( $_POST['page'] ?? 1 ) );
        $size  = min( 200, absint( $_POST['page_size'] ?? 50 ) );
        $conds = [];
        if ( $a  = sanitize_key( wp_unslash( $_POST['filter_action'] ?? ''  ) ) )        { $conds['action']       = $a; }
        if ( $ip = sanitize_text_field( wp_unslash( $_POST['filter_ip'] ?? '' ) ) )     { $conds['ip_address']   = $ip; }
        if ( $tl = sanitize_key( wp_unslash( $_POST['filter_threat'] ?? ''  ) ) )        { $conds['threat_level'] = $tl; }
        AjaxResponseBuilder::make()->success()->mergeData( (array) $this->t( 'firewall_logs' )->paginate( $page, 100, $conds, 'created_at', 'DESC' ) )->send();
    }

    public function handle_filter_logs(): void { $this->handle_get_logs_page(); }

    public function handle_delete_logs(): void {
        $this->cap();
        $ids   = array_map( 'absint', (array) wp_unslash($_POST['log_ids'] ?? [] ) );
        $count = 0;
        foreach ( $ids as $id ) { if ( $this->t( 'firewall_logs' )->delete( $id ) ) { $count++; } }
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d entry deleted', '%d entries deleted', $count, 'ultraguard-security-suite' ), $count ) )
			->addData( 'deleted', $count )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d entry deleted', '%d entries deleted', $count, 'ultraguard-security-suite' ), $count ), 'success' )
			->send();
    }

    public function handle_block_ip_from_log(): void {
        $this->cap();
        $log = $this->t( 'firewall_logs' )->get( absint( $_POST['log_id'] ?? 0 ) );
        if ( ! $log || empty( $log['ip_address'] ) ) { AjaxResponseBuilder::make()
			->error( __( 'Log entry not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Log entry not found', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $id = $this->t( 'ip_blacklist' )->block_ip( $log['ip_address'], 'blocked_from_log', 'manual', 'medium', null );
        if ( $id ) { AjaxResponseBuilder::make()
			/* translators: %s: string */
			->success( sprintf( __( 'IP %s blocked', 'ultraguard-security-suite' ), $log['ip_address'] ) )
			/* translators: %s: string */
			->addNotification( sprintf( __( 'IP %s blocked', 'ultraguard-security-suite' ), $log['ip_address'] ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed to block IP', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to block IP', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_whitelist_ip_from_log(): void {
        $this->cap();
        $log = $this->t( 'firewall_logs' )->get( absint( $_POST['log_id'] ?? 0 ) );
        if ( ! $log || empty( $log['ip_address'] ) ) { AjaxResponseBuilder::make()
			->error( __( 'Log entry not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Log entry not found', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $id = $this->t( 'ip_whitelist' )->add_ip( $log['ip_address'], 'whitelisted_from_log' );
        if ( $id ) { AjaxResponseBuilder::make()
			/* translators: %s: string */
			->success( sprintf( __( 'IP %s whitelisted', 'ultraguard-security-suite' ), $log['ip_address'] ) )
			/* translators: %s: string */
			->addNotification( sprintf( __( 'IP %s whitelisted', 'ultraguard-security-suite' ), $log['ip_address'] ), 'success' )
			->send(); }
        else { AjaxResponseBuilder::make()
			->error( __( 'Failed to whitelist IP', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to whitelist IP', 'ultraguard-security-suite' ), 'error' )
			->send(); }
    }

    public function handle_get_log_details(): void {
        $this->cap();
        $log = $this->t( 'firewall_logs' )->get( absint( $_POST['log_id'] ?? 0 ) );
        if ( ! $log ) { AjaxResponseBuilder::make()
			->error( __( 'Log not found', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Log not found', 'ultraguard-security-suite' ), 'error' )
			->send( 404); return; }
        AjaxResponseBuilder::make()
			->success()
			->mergeData( (array) $log )
			->send();
    }

    // =========================================================================
    // COUNTRY BLOCKING PAGE
    // =========================================================================

    public function handle_save_country_settings(): void {
        $this->cap();
        $mode      = sanitize_key( wp_unslash( $_POST['blocking_mode'] ?? 'none'  ) );
        $countries = array_map( 'sanitize_key', wp_unslash( (array) ( ( $_POST['countries'] ?? [] ) ) ) );
        update_option( 'ultraguard_country_blocking_mode', $mode );
        \UltraguardPro\ultraguard_pro()->update_option( 'blocked_countries', array_values( array_unique( $countries ) ) );
        do_action( 'ultraguard_country_settings_updated', $mode, $countries );
        AjaxResponseBuilder::make()
			->success( __( 'Country settings saved', 'ultraguard-security-suite' ) )
			->addData( 'saved_values', [
                'blocking_mode' => $mode,
                'countries'     => array_values( array_unique( $countries ) ),
            ] )
			->addNotification( __( 'Country settings saved', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_refresh_country_stats(): void {
        $this->cap();
        $blocked_countries = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );
        AjaxResponseBuilder::make()
			->success( __( 'Country stats refreshed', 'ultraguard-security-suite' ) )
			->addData( 'mode', \UltraguardPro\ultraguard_pro()->get_option( 'country_blocking_mode', 'none' ) )
			->addData( 'blocked_countries', count( $blocked_countries ) )
			->addData( 'geo_cache_entries', ( $t = $this->t( 'geolocation_data' ) ) ? $t->count() : 0 )
			->addNotification( __( 'Country stats refreshed', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_block_country(): void {
        $this->cap();
        $code = strtoupper( sanitize_key( wp_unslash( $_POST['country_code'] ?? ''  ) ) );
        if ( strlen( $code ) !== 2 ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid country code', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid country code', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $list = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );
        if ( ! in_array( $code, $list, true ) ) { $list[] = $code; }
        \UltraguardPro\ultraguard_pro()->update_option( 'blocked_countries', array_values( array_unique( $list ) ) );
        $names = $this->get_country_names();
        AjaxResponseBuilder::make()
			/* translators: %s: string */
			->success( sprintf( __( 'Country %s blocked', 'ultraguard-security-suite' ), $code ) )
			->addData( 'country_code', $code )
			->addData( 'country_name', $names[ $code ] ?? $code )
			/* translators: %s: string */
			->addNotification( sprintf( __( 'Country %s blocked', 'ultraguard-security-suite' ), $code ), 'success' )
			->send();
    }

    public function handle_unblock_country(): void {
        $this->cap();
        $code = strtoupper( sanitize_key( wp_unslash( $_POST['country_code'] ?? ''  ) ) );

        // Remove from blocked list.
        $blocked = array_values( array_filter(
            (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] ),
            fn( $c ) => strtoupper( $c ) !== $code
        ) );
        \UltraguardPro\ultraguard_pro()->update_option( 'blocked_countries', $blocked );

        // Keep disabled_countries in sync — remove the code there too.
        $disabled = array_values( array_filter(
            (array) \UltraguardPro\ultraguard_pro()->get_option( 'disabled_countries', [] ),
            fn( $c ) => strtoupper( $c ) !== $code
        ) );
        update_option( 'ultraguard_disabled_countries', $disabled );

        do_action( 'ultraguard_country_settings_updated' );

        AjaxResponseBuilder::make()
            /* translators: %s: string */
            ->success( sprintf( __( 'Country %s removed from list', 'ultraguard-security-suite' ), $code ) )
            ->addData( 'country_code', $code )
            /* translators: %s: string */
            ->addNotification( sprintf( __( 'Country %s removed from list', 'ultraguard-security-suite' ), $code ), 'success' )
            ->send();
    }

    public function handle_test_country_blocking(): void {
        $this->cap();
        $ip   = sanitize_text_field( wp_unslash( $_POST['test_ip'] ?? \UltraguardPro\core\Utils::get_client_ip( ) ) );
        $geo  = $this->t( 'geolocation_data' );
        $code = $geo ? $geo->get_country_code( $ip ) : null;
        $list = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );
        $mode = \UltraguardPro\ultraguard_pro()->get_option( 'country_blocking_mode', 'none' );
        $would_block = false;
        if ( $code ) {
            $would_block = ( $mode === 'blacklist' && in_array( $code, $list, true ) ) ||
                           ( $mode === 'whitelist' && ! in_array( $code, $list, true ) );
        }
        AjaxResponseBuilder::make()
			->success( $would_block ? __( 'This IP would be blocked', 'ultraguard-security-suite' ) : __( 'This IP would be allowed', 'ultraguard-security-suite' ) )
			->addData( 'ip', $ip )
			->addData( 'country_code', $code ?? 'unknown' )
			->addData( 'would_block', $would_block )
			->addData( 'mode', $mode )
			->addNotification( $would_block ? __( 'This IP would be blocked', 'ultraguard-security-suite' ) : __( 'This IP would be allowed', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_import_country_list(): void {
        $this->cap();
        $raw  = sanitize_textarea_field( wp_unslash( $_POST['country_list'] ?? '' ) );
        $list = array_values( array_unique( array_filter(
            array_map( fn( $c ) => strtoupper( trim( sanitize_key( $c ) ) ), explode( ',', $raw ) ),
            fn( $c ) => strlen( $c ) === 2
        ) ) );
        \UltraguardPro\ultraguard_pro()->update_option( 'blocked_countries', $list );
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d country imported', '%d countries imported', count( $list ), 'ultraguard-security-suite' ), count( $list ) ) )
			->addData( 'count', count( $list ) )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d country imported', '%d countries imported', count( $list ), 'ultraguard-security-suite' ), count( $list ) ), 'success' )
			->send();
    }

    public function handle_export_country_list(): void {
        $this->cap();
        $countries = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );
        AjaxResponseBuilder::make()
			->success( __( 'Country list exported', 'ultraguard-security-suite' ) )
			->addData( 'country_list', implode( ',', $countries ) )
			->addData( 'count', count( $countries ) )
			->addNotification( __( 'Country list exported', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_bulk_add_countries(): void {
        $this->cap();
        $region = sanitize_key( wp_unslash( $_POST['region'] ?? ''  ) );
        $map = [
            'eu'       => [ 'DE','FR','IT','ES','PL','NL','BE','SE','AT','CZ','RO','HU','PT','GR','FI','DK','SK','HR','LT','SI','LV','EE','CY','LU','MT' ],
            'apac'     => [ 'CN','JP','KR','IN','AU','SG','TH','MY','ID','PH','VN','HK','TW','NZ','BD','PK' ],
            'americas' => [ 'US','CA','BR','MX','AR','CO','CL','PE','VE','EC','BO','UY','PY','GY','SR' ],
            'africa'   => [ 'ZA','NG','EG','KE','GH','TZ','ET','UG','MA','CI','CM','SN','ZM','ZW','MW' ],
        ];
        $to_add = $map[ $region ] ?? [];
        if ( empty( $to_add ) ) { AjaxResponseBuilder::make()
			->error( __( 'Unknown region', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Unknown region', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        $merged = array_values( array_unique( array_merge( (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] ), $to_add ) ) );
        \UltraguardPro\ultraguard_pro()->update_option( 'blocked_countries', $merged );
        AjaxResponseBuilder::make()
			/* translators: %d: number */
			->success( sprintf( _n( '%d country added', '%d countries added', count( $to_add ), 'ultraguard-security-suite' ), count( $to_add ) ) )
			->addData( 'added', count( $to_add ) )
			->addData( 'total', count( $merged ) )
			/* translators: %d: number */
			->addNotification( sprintf( _n( '%d country added', '%d countries added', count( $to_add ), 'ultraguard-security-suite' ), count( $to_add ) ), 'success' )
			->send();
    }

    // =========================================================================
    // COUNTRY BLOCKING — STATUS TOGGLE & LIST REFRESH
    // =========================================================================

    /**
     * Toggle a single country's active/disabled state without removing it from
     * the list. Disabled countries remain stored in `blocked_countries` but are
     * also added to `ultraguard_disabled_countries`, which the enforcement layer
     * checks to skip individual entries at runtime.
     *
     * POST params:
     *   country_code  (string)  ISO 3166-1 alpha-2 code, e.g. "CN"
     *   set_active    (0|1)     1 = re-enable, 0 = disable
     */
    public function handle_toggle_country_status(): void {
        $this->cap();

        $code       = strtoupper( sanitize_key( wp_unslash( $_POST['country_code'] ?? ''  ) ) );
        $set_active = filter_var( wp_unslash( $_POST['set_active'] ?? '1' ), FILTER_VALIDATE_BOOLEAN ) ? 1 : 0;

        if ( strlen( $code ) !== 2 ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Invalid country code', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Invalid country code', 'ultraguard-security-suite' ), 'error' )
                ->send();
            return;
        }

        $blocked = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );

        if ( ! in_array( $code, array_map( 'strtoupper', $blocked ), true ) ) {
            AjaxResponseBuilder::make()
                /* translators: %s: string */
                ->error( sprintf( __( 'Country %s is not in the list', 'ultraguard-security-suite' ), $code ) )
                /* translators: %s: string */
                ->addNotification( sprintf( __( 'Country %s is not in the list', 'ultraguard-security-suite' ), $code ), 'error' )
                ->send();
            return;
        }

        $disabled = (array) \UltraguardPro\ultraguard_pro()->get_option( 'disabled_countries', [] );
        $disabled = array_map( 'strtoupper', $disabled );

        if ( $set_active ) {
            // Re-enable: remove from disabled list.
            $disabled = array_values( array_filter( $disabled, fn( $c ) => $c !== $code ) );
            /* translators: %s: string */
            $msg      = sprintf( __( 'Country %s enabled', 'ultraguard-security-suite' ), $code );
        } else {
            // Disable: add to disabled list if not already there.
            if ( ! in_array( $code, $disabled, true ) ) {
                $disabled[] = $code;
            }
            /* translators: %s: string */
            $msg = sprintf( __( 'Country %s disabled', 'ultraguard-security-suite' ), $code );
        }

        update_option( 'ultraguard_disabled_countries', array_values( $disabled ) );
        do_action( 'ultraguard_country_settings_updated' );

        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'country_code', $code )
            ->addData( 'is_active', $set_active )
            ->addNotification( $msg, 'success' )
            ->send();
    }

    /**
     * Return the current blocked countries list with per-entry status flags and full names.
     * Useful for refreshing the table client-side after bulk-add or save actions.
     */
    public function handle_get_blocked_countries(): void {
        $this->cap();

        $blocked       = (array) \UltraguardPro\ultraguard_pro()->get_option( 'blocked_countries', [] );
        $disabled      = array_map( 'strtoupper', (array) \UltraguardPro\ultraguard_pro()->get_option( 'disabled_countries', [] ) );
        $country_names = $this->get_country_names();

        $rows = array_map( function( $code ) use ( $disabled, $country_names ) {
            $code = strtoupper( (string) $code );
            return [
                'country_code' => $code,
                'country_name' => $country_names[ $code ] ?? $code,
                'is_active'    => ! in_array( $code, $disabled, true ),
            ];
        }, $blocked );

        AjaxResponseBuilder::make()
            ->success()
            ->addData( 'countries', array_values( $rows ) )
            ->addData( 'total', count( $rows ) )
            ->addData( 'mode', \UltraguardPro\ultraguard_pro()->get_option( 'country_blocking_mode', 'none' ) )
            ->send();
    }

    /**
     * Load the ISO 3166-1 alpha-2 country name map from the bundled JSON file.
     *
     * Returns [ 'US' => 'United States', 'IN' => 'India', … ] (249 entries).
     * Falls back to an empty array on read / parse failure so callers can
     * degrade gracefully (e.g. show the raw code instead of the full name).
     *
     * @return array<string,string>
     */
    private function get_country_names(): array {
        $json_path = defined( 'ULTRAGUARD_PRO_DIR' )
            ? trailingslashit( ULTRAGUARD_PRO_DIR ) . 'assets/json/country-codes.json'
            : '';

        if ( $json_path && file_exists( $json_path ) ) {
            $raw = json_decode( file_get_contents( $json_path ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions
            $map = [];
            foreach ( $raw['countries'] ?? [] as $entry ) {
                $map[ strtoupper( (string) $entry['code'] ) ] = (string) $entry['name'];
            }
            if ( ! empty( $map ) ) {
                return $map;
            }
        }

        return [];
    }


    // =========================================================================
    // ADD RULE PAGE
    // =========================================================================

    public function handle_save_add_new_rule(): void {
        $this->cap();
        $data               = $this->rule_fields();
        $data['created_by'] = get_current_user_id();
        $id = $this->t( 'firewall_rules' )->insert( $data );
        if ( $id ) {
            do_action( 'ultraguard_firewall_rule_saved', $id );
            AjaxResponseBuilder::make()
			->success()
			->addData( 'id', $id )
			->addNotification( __( 'Rule created', 'ultraguard-security-suite' ))
			->send();
        } else {
            AjaxResponseBuilder::make()
			->error( __( 'Failed to create rule', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Failed to create rule', 'ultraguard-security-suite' ), 'error' )
			->send();
        }
    }

    public function handle_test_firewall_rule(): void {
        $this->cap();
        $pattern  = sanitize_textarea_field( wp_unslash( $_POST['match_value']  ?? '' ) );
        $operator = sanitize_key( wp_unslash( $_POST['match_operator'] ?? 'equals'  ) );
        $test_val = sanitize_text_field( wp_unslash( $_POST['test_value'] ?? '' ) );

        if ( empty( trim( $pattern ) ) ) { AjaxResponseBuilder::make()
			->error( __( 'Pattern required', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Enter a match value to test this rule.', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }

        if ( $operator === 'regex' && @preg_match( $pattern, '' ) === false ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid regular expression', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid regular expression', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }

        if ( $test_val === '' ) { AjaxResponseBuilder::make()
			->success( __( 'Pattern is valid', 'ultraguard-security-suite' ) )
			->addData( 'valid', true )
			->addData( 'matches', null )
			->addNotification( __( 'Pattern is valid. Use Preview Effect to estimate matches from recent firewall logs.', 'ultraguard-security-suite' ), 'success' )
			->send(); return; }

        $matches = match ( $operator ) {
            'equals'      => $test_val === $pattern,
            'contains'    => str_contains( $test_val, $pattern ),
            'starts_with' => str_starts_with( $test_val, $pattern ),
            'ends_with'   => str_ends_with( $test_val, $pattern ),
            'regex'       => (bool) @preg_match( $pattern, $test_val ),
            default       => false,
        };
        AjaxResponseBuilder::make()
			->success( $matches ? __( 'Pattern MATCHES — rule would trigger', 'ultraguard-security-suite' ) : __( 'Pattern does NOT match', 'ultraguard-security-suite' ), )
			->addData( 'matches', $matches )
			->addNotification( $matches ? __( 'Pattern MATCHES — rule would trigger', 'ultraguard-security-suite' ) : __( 'Pattern does NOT match', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_save_rule_draft(): void {
        $this->cap();
        $draft              = $this->rule_fields();
        $draft['is_active'] = 0;
        set_transient( 'ultraguard_rule_draft_' . get_current_user_id(), $draft, DAY_IN_SECONDS );
        AjaxResponseBuilder::make()
			->success( __( 'Draft saved', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Draft saved', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_clear_rule_form(): void {
        $this->cap();
        delete_transient( 'ultraguard_rule_draft_' . get_current_user_id() );
        AjaxResponseBuilder::make()
			->success( __( 'Form cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Form cleared', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_validate_rule_pattern(): void {
        $this->cap();
        $pattern  = sanitize_textarea_field( wp_unslash( $_POST['match_value'] ?? '' ) );
        $operator = sanitize_key( wp_unslash( $_POST['match_operator'] ?? 'equals'  ) );
        if ( empty( trim( $pattern ) ) ) { AjaxResponseBuilder::make()
			->error( __( 'Pattern cannot be empty', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Pattern cannot be empty', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        if ( $operator === 'regex' && @preg_match( $pattern, '' ) === false ) { AjaxResponseBuilder::make()
			->error( __( 'Invalid regular expression', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Invalid regular expression', 'ultraguard-security-suite' ), 'error' )
			->send(); return; }
        AjaxResponseBuilder::make()
			->success( __( 'Pattern is valid', 'ultraguard-security-suite' ) )
			->addData( 'valid', true )
			->addNotification( __( 'Pattern is valid', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_preview_rule_effect(): void {
        $this->cap();
        $pattern  = sanitize_textarea_field( wp_unslash( $_POST['match_value'] ?? '' ) );
        $field    = sanitize_key( wp_unslash( $_POST['match_field']    ?? 'ip'  ) );
        $operator = sanitize_key( wp_unslash( $_POST['match_operator'] ?? 'equals'  ) );
        $recent   = $this->t( 'firewall_logs' ) ? $this->t( 'firewall_logs' )->get_by( [], 1000, 'created_at', 'DESC' ) : [];
        $hits     = 0;
        foreach ( $recent as $log ) {
            $subject = $field === 'ip' ? ( $log['ip_address'] ?? '' ) : ( $log['request_uri'] ?? '' );
            $hit = match ( $operator ) {
                'equals'      => $subject === $pattern,
                'contains'    => str_contains( $subject, $pattern ),
                'starts_with' => str_starts_with( $subject, $pattern ),
                'ends_with'   => str_ends_with( $subject, $pattern ),
                'regex'       => (bool) @preg_match( $pattern, $subject ),
                default       => false,
            };
            if ( $hit ) { $hits++; }
        }
        AjaxResponseBuilder::make()
			/* translators: %1$d: integer, %2$d: integer */
			->success( sprintf( __( 'Would match %1$d of the last %2$d requests', 'ultraguard-security-suite' ), $hits, count( $recent ) ) )
			->addData( 'would_match', $hits )
			->addData( 'total_checked', count( $recent ) )
			/* translators: %1$d: integer, %2$d: integer */
			->addNotification( sprintf( __( 'Would match %1$d of the last %2$d requests', 'ultraguard-security-suite' ), $hits, count( $recent ) ), 'success' )
			->send();
    }

    public function handle_use_template(): void {
        $this->cap();
        $rule = $this->get_template_rule( sanitize_key( wp_unslash( $_POST['template'] ?? ''  ) ) );
        if ( ! $rule ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Template not found', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Template not found', 'ultraguard-security-suite' ), 'error' )
                ->send();
            return;
        }

        // Map template fields onto the form field names used in the Rule Configuration card.
        // The 'form' action with operation='populate' calls handleForm() in ultraguard-managers.js,
        // which finds every [name="..."] input inside #firewall-add-rule-form and sets its value.
        $form_values = [
            'name'           => $rule['name']           ?? '',
            'description'    => $rule['description']    ?? '',
            'match_field'    => $rule['match_field']    ?? 'uri',
            'match_operator' => $rule['match_operator'] ?? 'contains',
            'match_value'    => $rule['match_value']    ?? '',
            'rule_action'    => $rule['action']         ?? 'block',
            'priority'       => $rule['priority']       ?? 100,
            'is_active'      => $rule['is_active']      ?? 1,
        ];

        AjaxResponseBuilder::make()
            /* translators: %s: string */
            ->success( sprintf( __( 'Template "%s" loaded — review and save.', 'ultraguard-security-suite' ), $rule['name'] ) )
            ->addData( 'rule', $rule )
            ->addAction( 'form', [
                'formId'    => 'firewall-add-rule-form',
                'operation' => 'populate',
                'data'      => $form_values,
            ] )
            ->addAction( 'scroll', [
                'target' => '#ultraguard-firewall-add-rule',
                'offset' => -20,
            ] )
            /* translators: %s: string */
            ->addNotification( sprintf( __( 'Template "%s" loaded — review and save.', 'ultraguard-security-suite' ), $rule['name'] ), 'success' )
            ->send();
    }

    // =========================================================================
    // SETTINGS PAGE
    // =========================================================================

    public function handle_save_firewall_settings(): void {
        $this->cap();

        // All configurable firewall option keys — boolean fields auto-cast
        $bool_keys = [
            'firewall_waf_enabled', 'firewall_sql_injection_scan', 'firewall_xss_scan',
            'firewall_directory_traversal', 'firewall_scan_uploads',
            'firewall_rfi_protection', 'firewall_rce_protection', 'firewall_xxe_protection',
            'firewall_ssrf_protection', 'firewall_cmdi_protection', 'firewall_lfi_protection',
            'firewall_rate_limiting', 'firewall_geo_blocking',
            'firewall_bot_protection', 'firewall_block_empty_ua', 'firewall_block_proxies',
            'firewall_auto_ban', 'firewall_reputation_sync',
            'firewall_login_throttle', 'firewall_hotlink_protection',
            'firewall_log_blocked_only',
        ];
        $int_keys = [
            'firewall_rate_limit_requests', 'firewall_rate_limit_window',
            'firewall_auto_ban_threshold', 'firewall_auto_ban_duration',
            'firewall_login_max_attempts', 'firewall_login_window_seconds',
            'firewall_log_retention_days',
        ];
        $text_keys = [
            'firewall_default_action', 'firewall_rate_limit_action', 'firewall_geo_mode',
        ];

        $saved = 0;

        foreach ( $bool_keys as $key ) {
            // Checkbox: present = true, absent = false
            $val = ! empty( $_POST[ $key ] ) ? '1' : '0'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Boolean presence check
            update_option( $key, $val );
            $saved++;
        }

        foreach ( $int_keys as $key ) {
            if ( array_key_exists( $key, $_POST ) ) {
                update_option( $key, absint( $_POST[ $key ] ) );
                $saved++;
            }
        }

        foreach ( $text_keys as $key ) {
            if ( array_key_exists( $key, $_POST ) ) {
                update_option( $key, sanitize_key( wp_unslash( $_POST[ $key ] ) ) );
                $saved++;
            }
        }

        do_action( 'ultraguard_firewall_settings_saved' );

        // Build saved_values map so the JS can refresh field states immediately.
        $saved_values = [];
        foreach ( $bool_keys as $key )  { $saved_values[ $key ] = get_option( $key ); }
        foreach ( $int_keys  as $key )  { if ( array_key_exists( $key, $_POST ) ) $saved_values[ $key ] = get_option( $key ); }
        foreach ( $text_keys as $key )  { if ( array_key_exists( $key, $_POST ) ) $saved_values[ $key ] = get_option( $key ); }

        AjaxResponseBuilder::make()
            ->success( __( 'Firewall settings saved.', 'ultraguard-security-suite' ) )
            ->addData( 'saved', $saved )
            ->addData( 'saved_values', $saved_values )
            ->addNotification( __( 'Firewall settings saved.', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    public function handle_test_firewall(): void {
        $this->cap();
        $score = 0;
        $waf   = (bool) get_option( 'firewall_waf_enabled', true );
        $rules = $this->t( 'firewall_rules' ) ? $this->t( 'firewall_rules' )->count( [ 'is_active' => 1 ] ) : 0;
        if ( $waf )      { $score += 40; }
        if ( $rules > 0 ) { $score += 30; }
        if ( $score > 0 ) { $score += 30; }  // basic connectivity
        AjaxResponseBuilder::make()
			/* translators: %d: integer */
			->success( sprintf( __( 'Firewall test complete — score: %d%%', 'ultraguard-security-suite' ), $score ) )
			->addData( 'score', $score )
			->addData( 'waf_enabled', $waf )
			->addData( 'active_rules', $rules )
			/* translators: %d: integer */
			->addNotification( sprintf( __( 'Firewall test complete — score: %d%%', 'ultraguard-security-suite' ), $score ), 'success' )
			->send();
    }

    /**
     * Re-seed attack patterns and default firewall rules from the bundled JSON
     * or the remote intelligence server (remote-first, local fallback).
     *
     * Replaces the old stub that only fired a hook and returned a static message.
     */
    public function handle_update_firewall_data(): void {
        $this->cap();

        $module = $this->plugin->modules->get( 'firewall' );
        if ( ! $module ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Firewall module unavailable.', 'ultraguard-security-suite' ) )
                ->send();
            return;
        }

        $results = $module->seed_firewall_data( true );

        $p = $results['patterns'];
        $r = $results['rules'];

        if ( ( $p['error'] ?? null ) && ( $r['error'] ?? null ) ) {
            AjaxResponseBuilder::make()
                ->error( sprintf(
                    /* translators: Placeholder values are inserted into this string. */
                    __( 'Seeding failed — Patterns: %1$s  Rules: %2$s', 'ultraguard-security-suite' ),
                    esc_html( $p['error'] ),
                    esc_html( $r['error'] )
                ) )
                ->send();
            return;
        }

        $ins_patterns = $p['inserted'] ?? 0;
        $ins_rules    = $r['inserted'] ?? 0;
        $source       = ( ( $p['source'] ?? '' ) === 'remote' || ( $r['source'] ?? '' ) === 'remote' )
                        ? 'remote server' : 'local bundle';

        do_action( 'ultraguard_firewall_rules_updated', $results );

        AjaxResponseBuilder::make()
            ->success( sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( 'Firewall data updated from %3$s: %1$d new attack patterns, %2$d new rules added.', 'ultraguard-security-suite' ),
                $ins_patterns, $ins_rules, $source
            ) )
            ->addData( 'patterns_inserted', $ins_patterns )
            ->addData( 'patterns_total',    $p['total']    ?? 0 )
            ->addData( 'rules_inserted',    $ins_rules )
            ->addData( 'rules_total',       $r['total']    ?? 0 )
            ->addData( 'source',            $source )
            ->addData( 'patterns_version',  $p['version']  ?? '' )
            ->addData( 'rules_version',     $r['version']  ?? '' )
            ->addNotification( __( 'Firewall data updated successfully.', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    /** @deprecated Use handle_update_firewall_data() — kept as shim. */
    public function handle_update_firewall_rules(): void {
        $this->handle_update_firewall_data();
    }

    public function handle_clear_firewall_cache(): void {
        $this->cap();
        $this->plugin->cache->flush_group( 'firewall' );
        AjaxResponseBuilder::make()
			->success( __( 'Firewall cache cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Firewall cache cleared', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_refresh_activity(): void {
        $this->cap();
        $recent = $this->t( 'firewall_logs' ) ? $this->t( 'firewall_logs' )->get_by( [], 10, 'created_at', 'DESC' ) : [];
        AjaxResponseBuilder::make()
			->success( __( 'Activity refreshed', 'ultraguard-security-suite' ) )
			->addData( 'activity', $recent )
			->addNotification( __( 'Activity refreshed', 'ultraguard-security-suite' ), 'success' )
			->send();
    }

    public function handle_download_export(): void { $this->handle_export_settings(); }

    // =========================================================================
    // PRIVATE HELPERS
    // =========================================================================

    private function rule_fields(): array {
        return [
            'name'           => sanitize_text_field( wp_unslash( $_POST['name']           ?? '' ) ),
            'description'    => sanitize_textarea_field( wp_unslash( $_POST['description'] ?? '' ) ),
            'rule_type'      => sanitize_key( wp_unslash( $_POST['rule_type']      ?? 'block'   ) ),
            'match_field'    => sanitize_key( wp_unslash( $_POST['match_field']    ?? 'ip'      ) ),
            'match_operator' => sanitize_key( wp_unslash( $_POST['match_operator'] ?? 'equals'  ) ),
            'match_value'    => sanitize_textarea_field( wp_unslash( $_POST['match_value'] ?? '' ) ),
            'action'         => sanitize_key( wp_unslash( $_POST['rule_action'] ?? 'block'  ) ),
            'priority'       => absint( $_POST['priority']       ?? 100 ),
            'is_active'      => (int) ! empty( $_POST['is_active'] ), // int cast is safe
        ];
    }

    private function sanitize_rule_array( array $r ): array {
        return [
            'name'           => sanitize_text_field( $r['name']           ?? '' ),
            'description'    => sanitize_textarea_field( $r['description'] ?? '' ),
            'rule_type'      => sanitize_key( $r['rule_type']      ?? 'block'  ),
            'match_field'    => sanitize_key( $r['match_field']    ?? 'ip'     ),
            'match_operator' => sanitize_key( $r['match_operator'] ?? 'equals' ),
            'match_value'    => sanitize_textarea_field( $r['match_value'] ?? '' ),
            'action'         => sanitize_key( $r['action']   ?? 'block' ),
            'priority'       => absint( $r['priority']       ?? 100 ),
            'is_active'      => (int) ( $r['is_active']      ?? 1 ),
        ];
    }

    private function get_template_rule( string $t ): ?array {
        $templates = [
            'block_xmlrpc'     => [ 'name' => 'Block XML-RPC',                'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'contains',    'match_value' => '/xmlrpc.php',         'action' => 'block', 'priority' => 5,  'is_active' => 1, 'description' => 'Block access to xmlrpc.php (brute-force vector)' ],
            'block_wp_admin'   => [ 'name' => 'Log wp-admin Access',          'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'starts_with', 'match_value' => '/wp-admin',           'action' => 'log',   'priority' => 50, 'is_active' => 1, 'description' => 'Log all wp-admin requests for auditing' ],
            'block_php_upload' => [ 'name' => 'Block PHP in Uploads',         'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'regex',       'match_value' => '/uploads/.*\.php$',  'action' => 'block', 'priority' => 3,  'is_active' => 1, 'description' => 'Prevent PHP execution in uploads directory' ],
            'block_sql'        => [ 'name' => 'Block SQL Injection',          'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'contains',    'match_value' => 'UNION+SELECT',        'action' => 'block', 'priority' => 1,  'is_active' => 1, 'description' => 'Block UNION SELECT SQL injection patterns' ],
            'block_user_enum'  => [ 'name' => 'Block User Enumeration',       'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'regex',       'match_value' => '\?author=\d+',      'action' => 'block', 'priority' => 20, 'is_active' => 1, 'description' => 'Block ?author=N username enumeration' ],
            'block_wp_config'  => [ 'name' => 'Block wp-config Access',       'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'contains',    'match_value' => 'wp-config.php',       'action' => 'block', 'priority' => 2,  'is_active' => 1, 'description' => 'Block direct requests to wp-config.php' ],
            'block_dotenv'     => [ 'name' => 'Block .env File Access',       'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'ends_with',   'match_value' => '.env',                'action' => 'block', 'priority' => 4,  'is_active' => 1, 'description' => 'Block access to .env configuration files' ],
            'block_backups'    => [ 'name' => 'Block Backup File Access',     'rule_type' => 'block', 'match_field' => 'uri',    'match_operator' => 'regex',       'match_value' => '\.(bak|sql|tar\.gz|zip)$', 'action' => 'block', 'priority' => 15, 'is_active' => 1, 'description' => 'Block .bak .sql .zip backup file downloads' ],
            'block_head'       => [ 'name' => 'Block HEAD Scanning',          'rule_type' => 'block', 'match_field' => 'method', 'match_operator' => 'equals',     'match_value' => 'HEAD',                'action' => 'log',   'priority' => 60, 'is_active' => 1, 'description' => 'Log HEAD requests used by path scanners' ],
        ];
        return $templates[ $t ] ?? null;
    }

    private function threat_protection_rules(): array {
        return [
            [ 'name' => 'Block SQL Injection',       'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'contains', 'match_value' => 'UNION+SELECT', 'action' => 'block', 'priority' => 1,  'is_active' => 1, 'description' => '' ],
            [ 'name' => 'Block XSS Attempt',         'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'contains', 'match_value' => '<script>',     'action' => 'block', 'priority' => 2,  'is_active' => 1, 'description' => '' ],
            [ 'name' => 'Block Directory Traversal', 'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'contains', 'match_value' => '../',          'action' => 'block', 'priority' => 3,  'is_active' => 1, 'description' => '' ],
            [ 'name' => 'Block XML-RPC',              'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'contains', 'match_value' => '/xmlrpc.php',  'action' => 'block', 'priority' => 10, 'is_active' => 1, 'description' => '' ],
        ];
    }

    private function wp_protection_rules(): array {
        return [
            [ 'name' => 'Protect wp-config.php', 'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'contains',    'match_value' => 'wp-config.php',    'action' => 'block', 'priority' => 1, 'is_active' => 1, 'description' => '' ],
            [ 'name' => 'Protect wp-includes',   'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'starts_with', 'match_value' => '/wp-includes/',    'action' => 'block', 'priority' => 2, 'is_active' => 1, 'description' => '' ],
            [ 'name' => 'Block PHP in Uploads',  'rule_type' => 'block', 'match_field' => 'uri', 'match_operator' => 'regex',       'match_value' => '/uploads/.*\.php$','action' => 'block', 'priority' => 3, 'is_active' => 1, 'description' => '' ],
        ];
    }

    // ── Row-level record mutations (table action buttons) ─────────────────────

    /**
     * Update all editable fields on a firewall rule.
     * POST: rule_id (int) + same fields as rule_fields()
     */
    public function handle_edit_rule(): void {
        $this->cap();
        $id = absint( $_POST['rule_id'] ?? 0 );
        if ( ! $id ) {
            AjaxResponseBuilder::make()->error( __( 'Invalid rule ID.', 'ultraguard-security-suite' ) )->send();
            return;
        }
        if ( $this->t( 'firewall_rules' )->update( $id, $this->rule_fields() ) ) {
            do_action( 'ultraguard_firewall_rule_saved', $id );
            AjaxResponseBuilder::make()
                ->success( __( 'Rule updated.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Saved!', 'ultraguard-security-suite' ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()->error( __( 'Failed to update rule.', 'ultraguard-security-suite' ) )->send();
        }
    }

    /**
     * Hard-delete a single firewall log entry.
     * POST: log_id (int)
     */
    public function handle_delete_log(): void {
        $this->cap();
        $id = absint( $_POST['log_id'] ?? 0 );
        if ( ! $id ) {
            AjaxResponseBuilder::make()->error( __( 'Invalid log ID.', 'ultraguard-security-suite' ) )->send();
            return;
        }
        if ( $this->t( 'firewall_logs' )->delete( $id ) ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Log entry deleted.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Log entry deleted.', 'ultraguard-security-suite' ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()->error( __( 'Failed to delete log entry.', 'ultraguard-security-suite' ) )->send();
        }
    }

    /**
     * Update reason / severity / expires_at on a blacklist record.
     * POST: blacklist_id (int), reason (string), severity (string), expires_at (string|empty)
     */
    public function handle_edit_blacklist(): void {
        $this->cap();
        $id       = absint( $_POST['blacklist_id'] ?? 0 );
        $reason   = sanitize_text_field( wp_unslash( $_POST['reason']   ?? '' ) );
        $severity = sanitize_key( wp_unslash( $_POST['severity'] ?? 'medium'  ) );
        $expires  = sanitize_text_field( wp_unslash( $_POST['expires_at'] ?? '' ) );

        if ( ! $id ) {
            AjaxResponseBuilder::make()->error( __( 'Invalid record ID.', 'ultraguard-security-suite' ) )->send();
            return;
        }
        $valid_severities = [ 'low', 'medium', 'high', 'critical' ];
        if ( ! in_array( $severity, $valid_severities, true ) ) { $severity = 'medium'; }

        $fields = [ 'reason' => $reason, 'severity' => $severity ];
        if ( $expires !== '' ) {
            $dt = \DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $expires, new \DateTimeZone( 'UTC' ) );
            if ( ! $dt ) {
                AjaxResponseBuilder::make()->error( __( 'Invalid date format — use YYYY-MM-DD HH:MM:SS.', 'ultraguard-security-suite' ) )->send();
                return;
            }
            $fields['expires_at'] = $expires;
        } else {
            $fields['expires_at'] = null;
        }

        if ( $this->t( 'ip_blacklist' )->update( $id, $fields ) ) {
            do_action( 'ultraguard_ip_list_updated' );
            AjaxResponseBuilder::make()
                ->success( __( 'Blacklist record updated.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Saved!', 'ultraguard-security-suite' ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()->error( __( 'Failed to update record.', 'ultraguard-security-suite' ) )->send();
        }
    }

    /**
     * Hard-delete a blacklist record by ID.
     * POST: blacklist_id (int)
     */
    public function handle_delete_blacklist_ip(): void {
        $this->cap();
        $id = absint( $_POST['blacklist_id'] ?? 0 );
        if ( ! $id ) {
            AjaxResponseBuilder::make()->error( __( 'Invalid record ID.', 'ultraguard-security-suite' ) )->send();
            return;
        }
        if ( $this->t( 'ip_blacklist' )->delete( $id ) ) {
            do_action( 'ultraguard_ip_list_updated' );
            AjaxResponseBuilder::make()
                ->success( __( 'Blacklist record deleted.', 'ultraguard-security-suite' ) )
                ->addRemove( '#ug-fw-blacklist-row-' . $id, true, 300 )
                ->addNotification( __( 'Record deleted.', 'ultraguard-security-suite' ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()->error( __( 'Failed to delete record.', 'ultraguard-security-suite' ) )->send();
        }
    }

    /**
     * Update label and is_active on a whitelist record.
     * POST: whitelist_id (int), label (string), is_active (0|1)
     */
    public function handle_edit_whitelist(): void {
        $this->cap();
        $id        = absint( $_POST['whitelist_id'] ?? 0 );
        $label     = sanitize_text_field( wp_unslash( $_POST['label'] ?? '' ) );
        $is_active = filter_var( wp_unslash( $_POST['is_active'] ?? '1' ), FILTER_VALIDATE_BOOLEAN ) ? 1 : 0;

        if ( ! $id ) {
            AjaxResponseBuilder::make()->error( __( 'Invalid record ID.', 'ultraguard-security-suite' ) )->send();
            return;
        }
        if ( $this->t( 'ip_whitelist' )->update( $id, [ 'label' => $label, 'is_active' => $is_active ] ) ) {
            do_action( 'ultraguard_ip_list_updated' );
            AjaxResponseBuilder::make()
                ->success( __( 'Whitelist record updated.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Saved!', 'ultraguard-security-suite' ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()->error( __( 'Failed to update record.', 'ultraguard-security-suite' ) )->send();
        }
    }

    // ── Private helpers ───────────────────────────────────────────────────────

    private function cap(): void {
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            AjaxResponseBuilder::make()->permissionError()->send( 403 );
        }
    }

    private function parse_ip_entries( string $raw ): array {
        return array_values( array_unique( array_filter( array_map(
            static fn( string $item ): string => trim( $item ),
            preg_split( '/[\r\n,]+/', $raw ) ?: []
        ) ) ) );
    }

    private function normalize_ip_entry( string $entry ): ?array {
        $entry = trim( $entry );
        if ( $entry === '' ) {
            return null;
        }

        if ( strpos( $entry, '/' ) === false ) {
            return filter_var( $entry, FILTER_VALIDATE_IP )
                ? [ 'ip_address' => $entry, 'ip_range' => null ]
                : null;
        }

        [ $ip, $mask ] = array_pad( explode( '/', $entry, 2 ), 2, '' );
        $ip   = trim( $ip );
        $mask = trim( $mask );

        if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) || ! ctype_digit( $mask ) ) {
            return null;
        }

        $bits = filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ? 128 : 32;
        $mask = (int) $mask;

        if ( $mask < 0 || $mask > $bits ) {
            return null;
        }

        return [
            'ip_address' => $ip,
            'ip_range'   => $ip . '/' . $mask,
        ];
    }

    private function import_blacklist_entries( array $items, string $reason, string $severity, ?string $expires_at ): int {
        $count = 0;
        $table = $this->t( 'ip_blacklist' );

        foreach ( $items as $item ) {
            $entry = $this->normalize_ip_entry( (string) $item );
            if ( ! $entry ) {
                continue;
            }

            $data = [
                'ip_address' => $entry['ip_address'],
                'ip_range'   => $entry['ip_range'],
                'reason'     => $reason,
                'source'     => 'manual',
                'severity'   => in_array( $severity, [ 'low', 'medium', 'high', 'critical' ], true ) ? $severity : 'medium',
                'is_active'  => 1,
                'expires_at' => $expires_at,
                'added_by'   => get_current_user_id() ?: null,
            ];

            $existing = $table->get_by_field( 'ip_address', $entry['ip_address'] );
            $saved    = $existing && ! empty( $existing['id'] )
                ? $table->update( (int) $existing['id'], $data )
                : $table->insert( $data );

            if ( $saved ) {
                $count++;
            }
        }

        return $count;
    }

    private function import_whitelist_entries( array $items, string $label ): int {
        $count = 0;
        $table = $this->t( 'ip_whitelist' );

        foreach ( $items as $item ) {
            $entry = $this->normalize_ip_entry( (string) $item );
            if ( ! $entry ) {
                continue;
            }

            $data = [
                'ip_address' => $entry['ip_address'],
                'ip_range'   => $entry['ip_range'],
                'label'      => $label,
                'is_active'  => 1,
                'added_by'   => get_current_user_id() ?: null,
            ];

            $existing = $table->get_by_field( 'ip_address', $entry['ip_address'] );
            $saved    = $existing && ! empty( $existing['id'] )
                ? $table->update( (int) $existing['id'], $data )
                : $table->insert( $data );

            if ( $saved ) {
                $count++;
            }
        }

        return $count;
    }

    private function t( string $key ) {
        return $this->plugin->modules->get_db_tables()->get_table( $key );
    }
    // ── Realtime fallback handler ─────────────────────────────────────────────

    /**
     * RT fallback: firewall_status channel.
     * Reuses the same table queries as handle_refresh_stats() so the data
     * shape is identical whether delivered via SSE or AJAX polling.
     */
    public function handle_rt_firewall_status(): void {
        if ( ! check_ajax_referer( 'ultraguard_admin_nonce', 'nonce', false ) ) {
            AjaxResponseBuilder::make()
                ->nonceError( __( 'Security check failed.', 'ultraguard-security-suite' ) )
                ->send( 403 );
        }
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            AjaxResponseBuilder::make()
                ->permissionError( __( 'Permission denied.', 'ultraguard-security-suite' ) )
                ->send( 403 );
            return;
        }

        $today = gmdate( 'Y-m-d 00:00:00' );

        $logs_tbl    = $this->t( 'firewall_logs' );
        $rules_tbl   = $this->t( 'firewall_rules' );
        $blocked_tbl = $this->t( 'ip_blacklist' );

        $blocked_today = $logs_tbl   ? (int) $logs_tbl->count( [ 'action' => 'blocked', 'created_at >=' => $today ] ) : 0;
        $blocked_total = $blocked_tbl ? (int) $blocked_tbl->count( [ 'is_active' => 1 ] ) : 0;
        $active_rules  = $rules_tbl  ? (int) $rules_tbl->count( [ 'is_active' => 1 ] ) : 0;

        $top_ip = '';
        if ( $logs_tbl && method_exists( $logs_tbl, 'get_top_ips' ) ) {
            $top    = $logs_tbl->get_top_ips( 1 );
            $top_ip = $top[0]['ip'] ?? '';
        }

        $recent_blocks = [];
        if ( $logs_tbl && method_exists( $logs_tbl, 'get_recent' ) ) {
            $recent_blocks = array_slice( (array) $logs_tbl->get_recent( 5 ), 0, 5 );
        }

        $fw_status = $active_rules > 0 ? 'active' : 'inactive';
        AjaxResponseBuilder::make()
            ->success()
            ->addData( 'status',        $fw_status )
            ->addData( 'status_label',  $active_rules > 0 ? __( 'Active', 'ultraguard-security-suite' ) : __( 'Inactive', 'ultraguard-security-suite' ) )
            ->addData( 'blocked_today', $blocked_today )
            ->addData( 'blocked_total', $blocked_total )
            ->addData( 'active_rules',  $active_rules )
            ->addData( 'top_threat_ip', $top_ip )
            ->addData( 'recent_blocks', $recent_blocks )
            ->updateText( '#ultraguard-fw-rt-status',        $fw_status === 'active' ? __( 'Active', 'ultraguard-security-suite' ) : __( 'Inactive', 'ultraguard-security-suite' ) )
            ->updateText( '#ultraguard-fw-rt-blocked-today', (string) $blocked_today )
            ->updateText( '#ultraguard-fw-rt-blocked-total', (string) $blocked_total )
            ->updateText( '#ultraguard-fw-rt-active-rules',  (string) $active_rules )
            ->send();
    }

}

// phpcs:enable
