<?php
/**
 * NotificationsAjax — All wp_ajax_{action} handlers for the Notifications module.
 *
 * Security layers on EVERY handler:
 *   1. check_ajax_referer( NONCE, 'nonce' )  — CSRF
 *   2. current_user_can( 'manage_options' )   — privilege
 *   3. LicenseInfo::instance()->isPro()       — Pro license gate
 *
 * All actions use the shared nonce: 'ultraguard_admin_nonce'.
 * Action prefix: 'ultraguard_notif_' to avoid collisions.
 *
 * @package UltraguardPro\modules\notifications\ajax
 * @since   6.5.0
 */

namespace UltraguardPro\modules\notifications\ajax;

use UltraguardPro\modules\core\ModuleAjaxSettingsTrait;

use UltraguardPro\Plugin;
use UltraguardPro\support\LicenseInfo;
use UltraguardPro\modules\notifications\NotificationsDispatcher;
use UltraguardPro\support\AjaxResponseBuilder;

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

// phpcs:disable WordPress.Security.NonceVerification.Missing -- Notifications AJAX handlers verify nonce centrally via $this->verify().

class NotificationsAjax {

    use ModuleAjaxSettingsTrait;

    /** Shared nonce action for all Notifications AJAX calls. */
    private const NONCE = 'ultraguard_admin_nonce';

    /** @var Plugin */
    private Plugin $plugin;

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

    // ── Registration ───────────────────────────────────────────────────────────

    public function register(): void {
        $map = [
            // Rules management
            'ultraguard_notif_get_rules'      => 'handle_get_rules',
            'ultraguard_notif_save_rule'       => 'handle_save_rule',
            'ultraguard_notif_delete_rule'     => 'handle_delete_rule',
            'ultraguard_notif_toggle_rule'     => 'handle_toggle_rule',
            'ultraguard_notif_test_rule'       => 'handle_test_rule',
            'ultraguard_notif_bulk_toggle'     => 'handle_bulk_toggle_rules',
            'ultraguard_notif_bulk_delete'     => 'handle_bulk_delete_rules',

            // Channel management
            'ultraguard_notif_get_channels'    => 'handle_get_channels',
            'ultraguard_notif_save_channel'    => 'handle_save_channel',
            'ultraguard_notif_delete_channel'  => 'handle_delete_channel',
            'ultraguard_notif_test_channel'    => 'handle_test_channel',

            // Log management
            'ultraguard_notif_get_log'         => 'handle_get_log',
            'ultraguard_notif_export_log'      => 'handle_export_log',
            'ultraguard_notif_clear_log'       => 'handle_clear_log',
            'ultraguard_notif_retry_failed'    => 'handle_retry_failed',
            'ultraguard_notif_delete_log_entry'=> 'handle_delete_log_entry',

            // Stats / dashboard
            'ultraguard_notif_refresh_stats'   => 'handle_refresh_stats',
            'ultraguard_notif_get_stats'       => 'handle_refresh_stats',

            // Settings
            'ultraguard_notif_save_settings'   => 'handle_save_settings',
            'ultraguard_notif_send_test'       => 'handle_send_test',

            // Digest
            'ultraguard_notif_send_digest_now' => 'handle_send_digest_now',
        ];

        foreach ( $map as $action => $method ) {
            add_action( 'wp_ajax_' . $action, [ $this, $method ] );
        }
    }

    // ── Rules ──────────────────────────────────────────────────────────────────

    public function handle_get_rules(): void {
        $this->verify();
        $t    = $this->t( 'notification_rules' );
        $page = max( 1, absint( $_POST['page'] ?? 1 ) );
        AjaxResponseBuilder::make()->success()->mergeData( (array) ( $t ? $t->paginate( $page, 50, [], 'name', 'ASC' ) : [ 'records' => [], 'total' => 0 ] ) )->send();
    }

    public function handle_save_rule(): void {
        $this->verify();

        $t       = $this->t( 'notification_rules' );
        $rule_id = absint( $_POST['rule_id'] ?? 0 );

        $channel = sanitize_key( wp_unslash( $_POST['channel'] ?? 'email'  ) );

        // Free plan: only email channel is allowed
        if ( ! \UltraguardPro\support\LicenseInfo::instance()->isPro() && $channel !== 'email' ) {
            $this->require_pro_feature( 'slack_notifications', __( 'add Slack or webhook notification channels', 'ultraguard-security-suite' ) );
            return;
        }

        $data = [
            'name'               => sanitize_text_field( wp_unslash( $_POST['name']               ?? '' ) ),
            'event_type'         => sanitize_key( wp_unslash( $_POST['event_type']                ?? ''  ) ),
            'severity_threshold' => sanitize_key( wp_unslash( $_POST['severity_threshold']        ?? 'medium'  ) ),
            'channel'            => $channel,
            'recipient'          => sanitize_text_field( wp_unslash( $_POST['recipient']          ?? get_option( 'admin_email' ) ) ),
            'subject_template'   => sanitize_text_field( wp_unslash( $_POST['subject_template']   ?? '' ) ),
            'message_template'   => wp_kses_post( wp_unslash( $_POST['message_template'] ?? '' ) ),
            'webhook_secret'     => sanitize_text_field( wp_unslash( $_POST['webhook_secret']     ?? '' ) ) ?: null,
            'is_active'          => filter_var( wp_unslash( $_POST['is_active'] ?? '1' ), FILTER_VALIDATE_BOOLEAN ) ? 1 : 0,
            'cooldown_minutes'   => max( 0, absint( $_POST['cooldown_minutes']        ?? 60 ) ),
        ];

        if ( empty( $data['name'] ) || empty( $data['event_type'] ) || empty( $data['recipient'] ) ) {
            AjaxResponseBuilder::make()->error( __( 'Name, event type and recipient are required.', 'ultraguard-security-suite' ) )->addNotification( __( 'Name, event type and recipient are required.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        if ( $rule_id ) {
            $ok  = $t && $t->update( $rule_id, $data );
            $result = $ok ? $rule_id : false;
        } else {
            $data['created_by'] = get_current_user_id();
            $result = $t ? $t->insert( $data ) : false;
        }

        if ( $result ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Rule saved.', 'ultraguard-security-suite' ) )
                ->addData( 'id', $result )
                ->addNotification( __( 'Rule saved!', 'ultraguard-security-suite' ), 'success' )
                ->closeModal( 'ultraguard-notif-rule-modal' )
                ->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->verify();
        $id = absint( $_POST['rule_id'] ?? 0 );
        $t  = $this->t( 'notification_rules' );
        if ( $t && $t->delete( $id ) ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Rule deleted.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Rule deleted.', 'ultraguard-security-suite' ), 'info' )
                ->addRemove( '#ug-notif-rule-row-' . $id, true, 300 )
                ->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->verify();
        $id   = absint( $_POST['rule_id'] ?? 0 );
        $t    = $this->t( 'notification_rules' );
        $rule = $t ? $t->get( $id ) : null;
        if ( ! $rule || 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;
        $t->update( $id, [ 'is_active' => $new ] );
        $msg = $new ? __( 'Rule enabled.', 'ultraguard-security-suite' ) : __( 'Rule paused.', 'ultraguard-security-suite' );
        $row_sel = '#ug-notif-rule-row-' . $id;
        $builder = AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'is_active', $new )
            ->addNotification( $msg, $new ? 'success' : 'info' )
            ->updateText( $row_sel . ' .ultraguard-rule-status', $new ? __( 'Active', 'ultraguard-security-suite' ) : __( 'Paused', 'ultraguard-security-suite' ) )
            ->updateAttribute( $row_sel . ' .ultraguard-rule-toggle', 'data-active', (string) $new );
        if ( $new ) {
            $builder->removeClass( $row_sel, 'rule-paused' )->addClass( $row_sel, 'rule-active' );
        } else {
            $builder->removeClass( $row_sel, 'rule-active' )->addClass( $row_sel, 'rule-paused' );
        }
        $builder->send();
    }

    public function handle_test_rule(): void {
        $this->verify();
        $rule_id = absint( $_POST['rule_id'] ?? 0 );
        $t       = $this->t( 'notification_rules' );
        $rule    = $t ? $t->get( $rule_id ) : null;

        if ( ! $rule || empty( $rule['id'] ) ) {
            AjaxResponseBuilder::make()->error( __( 'Rule not found.', 'ultraguard-security-suite' ) )->addNotification( __( 'Rule not found.', 'ultraguard-security-suite' ), 'error' )->send(); return;
        }

        $context = [
            'event_type'  => $rule['event_type'],
            'severity'    => $rule['severity_threshold'],
            'ip'          => \UltraguardPro\core\Utils::get_client_ip(),
            'time'        => current_time( 'mysql', true ),
            'count'       => 1,
            'username'    => wp_get_current_user()->user_login ?? 'test_user',
            'site_name'   => get_bloginfo( 'name' ),
            'site_url'    => home_url(),
            'admin_url'   => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=notifications' ),
        ];

        $sent = $this->dispatcher()->dispatch( $rule, $context, '[TEST] ' );

        if ( $sent ) {
            AjaxResponseBuilder::make()
                /* translators: %s: string */
                ->success( sprintf( __( 'Test notification sent to %s.', 'ultraguard-security-suite' ), esc_html( $rule['recipient'] ) ) )
                /* translators: %s: string */
                ->addNotification( sprintf( __( 'Test sent to %s.', 'ultraguard-security-suite' ), esc_html( $rule['recipient'] ) ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Failed to send test notification. Check channel settings.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Test failed. Check channel settings.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    public function handle_bulk_toggle_rules(): void {
        $this->verify();
        $ids   = array_map( 'absint', (array) wp_unslash( $_POST['rule_ids'] ?? [] ) );
        $t     = $this->t( 'notification_rules' );
        $count = 0;
        if ( $t ) {
            foreach ( $ids as $id ) {
                $rule = $t->get( $id );
                if ( $rule ) { $t->update( $id, [ 'is_active' => $rule['is_active'] ? 0 : 1 ] ); $count++; }
            }
        }
        /* translators: %d: number */
        $msg = sprintf( _n( '%d rule toggled.', '%d rules toggled.', $count, 'ultraguard-security-suite' ), $count );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'toggled', $count )
            ->addNotification( $msg, 'info' )
            ->addReload( 800 )
            ->send();
    }

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

    // ── Channels ───────────────────────────────────────────────────────────────

    public function handle_get_channels(): void {
        $this->verify();
        $t    = $this->t( 'notification_channels' );
        $page = max( 1, absint( $_POST['page'] ?? 1 ) );
        AjaxResponseBuilder::make()->success()->mergeData( (array) ( $t ? $t->paginate( $page, 50, [], 'name', 'ASC' ) : [ 'records' => [], 'total' => 0 ] ) )->send();
    }

    public function handle_save_channel(): void {
        $this->verify();
        $t  = $this->t( 'notification_channels' );
        $id = absint( $_POST['channel_id'] ?? 0 );

        $data = [
            'name'           => sanitize_text_field( wp_unslash( $_POST['name']           ?? '' ) ),
            'channel_type'   => sanitize_key( wp_unslash( $_POST['channel_type']          ?? 'email'  ) ),
            'address'        => sanitize_text_field( wp_unslash( $_POST['address']        ?? '' ) ),
            'webhook_secret' => sanitize_text_field( wp_unslash( $_POST['webhook_secret'] ?? '' ) ) ?: null,
            'is_active'      => filter_var( wp_unslash( $_POST['is_active'] ?? '1' ), FILTER_VALIDATE_BOOLEAN ) ? 1 : 0,
        ];

        if ( empty( $data['name'] ) || empty( $data['address'] ) ) {
            AjaxResponseBuilder::make()->error( __( 'Name and address are required.', 'ultraguard-security-suite' ) )->addNotification( __( 'Name and address are required.', 'ultraguard-security-suite' ), 'error' )->send(); return;
        }

        if ( $id ) {
            $ok     = $t && $t->update( $id, $data );
            $result = $ok ? $id : false;
        } else {
            $data['created_by'] = get_current_user_id();
            $result = $t ? $t->insert( $data ) : false;
        }

        if ( $result ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Channel saved.', 'ultraguard-security-suite' ) )
                ->addData( 'id', $result )
                ->addNotification( __( 'Channel saved!', 'ultraguard-security-suite' ), 'success' )
                ->closeModal( 'ultraguard-notif-channel-modal' )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Failed to save channel.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Failed to save channel.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    public function handle_delete_channel(): void {
        $this->verify();
        $id = absint( $_POST['channel_id'] ?? 0 );
        $t  = $this->t( 'notification_channels' );
        if ( $t && $t->delete( $id ) ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Channel deleted.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Channel deleted.', 'ultraguard-security-suite' ), 'info' )
                ->addRemove( '#ug-notif-channel-row-' . $id, true, 300 )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Failed to delete channel.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Failed to delete channel.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    public function handle_test_channel(): void {
        $this->verify();
        $id      = absint( $_POST['channel_id'] ?? 0 );
        $t       = $this->t( 'notification_channels' );
        $channel = $t ? $t->get( $id ) : null;

        if ( ! $channel || empty( $channel['id'] ) ) {
            AjaxResponseBuilder::make()->error( __( 'Channel not found.', 'ultraguard-security-suite' ) )->addNotification( __( 'Channel not found.', 'ultraguard-security-suite' ), 'error' )->send(); return;
        }

        $sent = $this->dispatcher()->send_test( $channel['channel_type'], $channel['address'] );

        if ( $t ) { $t->record_test( $id, $sent ); }

        if ( $sent ) {
            AjaxResponseBuilder::make()
                /* translators: %s: string */
                ->success( sprintf( __( 'Test sent to %s.', 'ultraguard-security-suite' ), esc_html( $channel['address'] ) ) )
                /* translators: %s: string */
                ->addNotification( sprintf( __( 'Test sent to %s.', 'ultraguard-security-suite' ), esc_html( $channel['address'] ) ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Test failed. Check the channel address and settings.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Test failed. Check channel address and settings.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    // ── Log ────────────────────────────────────────────────────────────────────

    public function handle_get_log(): void {
        $this->verify();
        $t       = $this->t( 'notification_log' );
        $page    = max( 1, absint( $_POST['page'] ?? 1 ) );
        $status  = sanitize_key( wp_unslash( $_POST['filter_status']  ?? ''  ) );
        $channel = sanitize_key( wp_unslash( $_POST['filter_channel'] ?? ''  ) );
        $conds   = [];
        if ( $status  ) { $conds['status']  = $status; }
        if ( $channel ) { $conds['channel'] = $channel; }
        AjaxResponseBuilder::make()->success()->mergeData( (array) ( $t ? $t->paginate( $page, 50, $conds, 'created_at', 'DESC' ) : [ 'records' => [], 'total' => 0 ] ) )->send();
    }

    public function handle_export_log(): void {
        $this->verify();
        $t   = $this->t( 'notification_log' );
        $csv = $t ? $t->export_to_csv(
            [],
            [ 'event_type', 'channel', 'recipient', 'subject', 'status', 'error_message', 'sent_at', 'created_at' ],
            100000
        ) : '';
        $url = $this->save_export( 'notification-log-' . gmdate( 'Y-m-d-His' ) . '.csv', $csv );
        AjaxResponseBuilder::make()->success( __( 'Log exported.', 'ultraguard-security-suite' ) )->addData( 'download_url', $url )->addNotification( __( 'Log exported.', 'ultraguard-security-suite' ), 'success' )->download( $url )->send();
    }

    public function handle_clear_log(): void {
        $this->verify();
        $t = $this->t( 'notification_log' );
        if ( $t ) { $t->truncate(); }
        AjaxResponseBuilder::make()
            ->success( __( 'Notification log cleared.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Log cleared!', 'ultraguard-security-suite' ), 'success' )
            ->emptyElement( '#ultraguard-notif-log-table tbody' )
            ->updateText( '#ultraguard-notif-log-count', '0' )
            ->send();
    }

    public function handle_retry_failed(): void {
        $this->verify();
        $t      = $this->t( 'notification_log' );
        $rt     = $this->t( 'notification_rules' );
        $failed = $t ? $t->get_failed( 50 ) : [];
        $retried= 0;

        foreach ( $failed as $entry ) {
            $rule_id = (int) ( $entry['rule_id'] ?? 0 );
            $rule    = ( $rt && $rule_id ) ? $rt->get( $rule_id ) : null;

            if ( $rule && ! empty( $rule['id'] ) ) {
                $context = [
                    'event_type' => $entry['event_type'] ?? 'unknown',
                    'severity'   => 'medium',
                    'time'       => current_time( 'mysql', true ),
                    'ip'         => '',
                ];
                // Decode stored context JSON if available
                if ( ! empty( $entry['context_json'] ) ) {
                    $decoded = json_decode( $entry['context_json'], true );
                    if ( is_array( $decoded ) ) { $context = array_merge( $context, $decoded ); }
                }
                $this->dispatcher()->dispatch( $rule, $context, '[RETRY] ' );
                if ( $t ) { $t->update( (int) $entry['id'], [ 'status' => 'queued' ] ); }
                $retried++;
            }
        }

        /* translators: %d: number */
        $msg = sprintf( _n( '%d notification queued for retry.', '%d notifications queued for retry.', $retried, 'ultraguard-security-suite' ), $retried );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'count', $retried )
            ->addNotification( $msg, $retried > 0 ? 'info' : 'warning' )
            ->send();
    }

    public function handle_delete_log_entry(): void {
        $this->verify();
        $id = absint( $_POST['log_id'] ?? 0 );
        $t  = $this->t( 'notification_log' );
        if ( $t && $t->delete( $id ) ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Log entry deleted.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Log entry deleted.', 'ultraguard-security-suite' ), 'info' )
                ->addRemove( '#ug-notif-log-row-' . $id, true, 300 )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Failed to delete log entry.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Failed to delete log entry.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    // ── Stats ──────────────────────────────────────────────────────────────────

    public function handle_refresh_stats(): void {
        $this->verify();
        $log_t   = $this->t( 'notification_log' );
        $rules_t = $this->t( 'notification_rules' );
        $today   = gmdate( 'Y-m-d 00:00:00' );

        $summary = $log_t ? $log_t->get_stats_summary() : [];

        $sent_today   = $log_t   ? $log_t->count(   [ 'status' => 'sent', 'sent_at >=' => $today ] ) : 0;
        $active_rules = $rules_t ? $rules_t->count( [ 'is_active' => 1 ] )                           : 0;
        $total_rules  = $rules_t ? $rules_t->count()                                                  : 0;

        AjaxResponseBuilder::make()
            ->success( __( 'Stats refreshed.', 'ultraguard-security-suite' ) )
            ->mergeData( array_merge( $summary, [
                'sent_today'   => $sent_today,
                'active_rules' => $active_rules,
                'total_rules'  => $total_rules,
                'channel_chart'=> $log_t ? $log_t->get_pie_chart_data( 'channel', 'id', 'COUNT', [], 6 ) : [],
                'hourly_chart' => $log_t ? $log_t->get_chart_data( 'sent_at', 'id', 'hour', [ 'status' => 'sent' ], 24 ) : [],
            ] ) )
            ->updateText( '#ultraguard-notif-stat-sent-today',   (string) $sent_today )
            ->updateText( '#ultraguard-notif-stat-active-rules', (string) $active_rules )
            ->updateText( '#ultraguard-notif-stat-total-rules',  (string) $total_rules )
            ->send();
    }

    // ── Settings ──────────────────────────────────────────────────────────────

    private function field_schema(): array {
        return [
            'notifications_enabled'         => [ 'type' => 'bool' ],
            'notifications_email_enabled'   => [ 'type' => 'bool' ],
            'notifications_webhook_enabled' => [ 'type' => 'bool' ],
            'notifications_slack_enabled'   => [ 'type' => 'bool' ],
            'notifications_digest_mode'     => [ 'type' => 'bool' ],
            'notifications_log_enabled'     => [ 'type' => 'bool' ],
            'notifications_retry_failed'    => [ 'type' => 'bool' ],
            'notifications_notify_on_login' => [ 'type' => 'bool' ],
            'notifications_digest_frequency' => [ 'type' => 'select', 'options' => [ 'hourly', 'twicedaily', 'daily', 'weekly' ], 'default' => 'daily' ],
            'notifications_webhook_format'  => [ 'type' => 'select', 'options' => [ 'json', 'slack', 'form' ],                    'default' => 'json'  ],
            'notifications_from_email'      => [ 'type' => 'email', 'allow_empty' => true, 'default' => '' ],
            'notifications_from_name'       => [ 'type' => 'text',  'default' => '' ],
            'notifications_admin_email'     => [ 'type' => 'email', 'allow_empty' => true, 'default' => '' ],
            'notifications_webhook_url'     => [ 'type' => 'url',   'allow_empty' => true, 'default' => '' ],
            'notifications_webhook_secret'  => [ 'type' => 'text',  'default' => '' ],
            'notifications_slack_webhook'   => [ 'type' => 'url',   'allow_empty' => true, 'default' => '' ],
            'notifications_global_cooldown' => [ 'type' => 'int', 'min' => 0,  'max' => 1440, 'default' => 60 ],
            'notifications_max_per_hour'    => [ 'type' => 'int', 'min' => 1,  'max' => 1000, 'default' => 50 ],
            'notifications_log_retention'   => [ 'type' => 'int', 'min' => 1,  'max' => 365,  'default' => 90 ],
        ];
    }

    public function handle_save_settings(): void {
        $this->verify();

        $data = $this->sanitize_fields( (array) wp_unslash( $_POST ), $this->field_schema() );
        $ok   = $this->plugin->settings->save_module_settings( 'notifications', $data );

        if ( ! $ok ) {
            AjaxResponseBuilder::make()->error( __( 'Failed to save notification settings.', 'ultraguard-security-suite' ) )->addNotification( __( 'Failed to save notification settings.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        do_action( 'ultraguard_notifications_settings_saved' );

        // Return saved values so the JS can refresh field states
        $saved_values = [];
        foreach ( array_keys( $data ) as $k ) {
            $saved_values[ $k ] = $this->plugin->settings->get_option( $k );
        }

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

    public function handle_send_test(): void {
        $this->verify();
        $channel   = sanitize_key( wp_unslash( $_POST['channel']   ?? 'email'  ) );
        $recipient = sanitize_text_field( wp_unslash( $_POST['recipient'] ?? get_option( 'admin_email' ) ) );

        $sent = $this->dispatcher()->send_test( $channel, $recipient );

        if ( $sent ) {
            AjaxResponseBuilder::make()
                /* translators: %1$s: string, %2$s: string */
                ->success( sprintf( __( 'Test %1$s notification sent to %2$s.', 'ultraguard-security-suite' ), $channel, esc_html( $recipient ) ) )
                /* translators: %1$s: string, %2$s: string */
                ->addNotification( sprintf( __( 'Test %1$s sent to %2$s.', 'ultraguard-security-suite' ), $channel, esc_html( $recipient ) ), 'success' )
                ->send();
        } else {
            AjaxResponseBuilder::make()
                ->error( __( 'Test failed. Verify channel settings.', 'ultraguard-security-suite' ) )
                ->addNotification( __( 'Test failed. Verify channel settings.', 'ultraguard-security-suite' ), 'error' )
                ->send();
        }
    }

    public function handle_send_digest_now(): void {
        $this->verify();
        do_action( 'ultraguard_notifications_digest' );
        AjaxResponseBuilder::make()
            ->success( __( 'Digest email dispatch triggered.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Digest queued for dispatch.', 'ultraguard-security-suite' ), 'info' )
            ->send();
    }

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

    /**
     * Verify nonce and capability. Dies on failure.
     */
    private function verify(): void {
        if ( ! check_ajax_referer( self::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 );
        }
        if ( ! LicenseInfo::instance()->isPro() ) {
            \UltraguardPro\support\UpgradeManager::soft_gate_response(
                'notification_rules',
                __( 'manage custom notification rules', 'ultraguard-security-suite' )
            );
        }
    }

    /**
     * @param string $key
     * @return \UltraguardPro\db\BaseTable|null
     */
    private function t( string $key ) {
        $db = $this->plugin->modules->get_db_tables();
        return $db ? $db->get_table( $key ) : null;
    }

    /**
     * @return NotificationsDispatcher
     */
    private function dispatcher(): NotificationsDispatcher {
        static $d = null;
        if ( null === $d ) { $d = new NotificationsDispatcher( $this->plugin ); }
        return $d;
    }

    /**
     * Save an export file and return its public URL.
     *
     * @param  string $filename
     * @param  string $content
     * @return string
     */
    private function save_export( string $filename, string $content ): string {
        $upload = wp_upload_dir();
        $dir    = trailingslashit( $upload['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $dir );
        if ( ! file_exists( $dir . 'index.php' ) ) {
            file_put_contents( $dir . 'index.php', '<?php // silence' );
        }
        file_put_contents( $dir . $filename, $content );
        return trailingslashit( $upload['baseurl'] ) . 'ultraguard-security-suite/exports/' . $filename;
    }
}

// phpcs:enable
