<?php
/**
 * ServerCommunicationAjax — wp_ajax_ handlers for the Server Intelligence UI.
 *
 * Security contract on EVERY handler:
 *   1. check_ajax_referer( NONCE, 'nonce' ) — CSRF protection
 *   2. site admin or network admin capability check — privilege gate
 *   3. Input sanitisation before use
 *
 * Registered actions:
 *   ultraguard_si_status          — current sync status + queue length
 *   ultraguard_si_pull_now        — trigger an immediate pull for one resource
 *   ultraguard_si_pull_all        — trigger all pulls immediately
 *   ultraguard_si_flush_queue     — flush pending push queue now
 *   ultraguard_si_clear_queue     — discard all queued push events
 *   ultraguard_si_threat_summary  — get threat IP table stats
 *   ultraguard_si_purge_old_ips   — purge expired / old threat IPs
 *   ultraguard_si_test_push       — send a test push event to the server
 *   ultraguard_si_test_pull       — test connectivity to the pull API
 *   ultraguard_si_save_settings   — save per-feed auto-sync settings
 *
 * @package UltraguardPro\support
 * @since   6.6.0
 */

namespace UltraguardPro\support;

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

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

class ServerCommunicationAjax {

    private const NONCE = 'ultraguard_admin_nonce';

    private Plugin               $plugin;
    private ServerCommunication  $si;

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

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

    public function register(): void {
        $map = [
            'ultraguard_si_status'         => 'handle_status',
            'ultraguard_si_pull_now'        => 'handle_pull_now',
            'ultraguard_si_pull_all'        => 'handle_pull_all',
            'ultraguard_si_flush_queue'     => 'handle_flush_queue',
            'ultraguard_si_clear_queue'     => 'handle_clear_queue',
            'ultraguard_si_threat_summary'  => 'handle_threat_summary',
            'ultraguard_si_purge_old_ips'   => 'handle_purge_old_ips',
            'ultraguard_si_test_push'       => 'handle_test_push',
            'ultraguard_si_test_pull'       => 'handle_test_pull',
            'ultraguard_si_save_settings'   => 'handle_save_settings',
        ];

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

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

    /**
     * Return current sync status overview.
     */
    public function handle_status(): void {
        $this->authorize();

        $queue      = (array) get_option( 'ultraguard_push_queue', [] );
        $last_pull  = (int)   get_option( 'ultraguard_si_last_pull', 0 );
        $is_pro     = LicenseInfo::instance()->isPro();
        $free_delay = LicenseInfo::instance()->getUpdateDelay();

        AjaxResponseBuilder::make()
            ->success( __( 'Status retrieved.', 'ultraguard-security-suite' ) )
            ->addData( 'is_pro',              $is_pro )
            ->addData( 'free_delay_days',     $free_delay )
            ->addData( 'push_queue_size',     count( $queue ) )
            ->addData( 'last_pull_ts',        $last_pull )
            ->addData( 'last_pull_human',     $last_pull ? human_time_diff( $last_pull ) . ' ago' : __( 'Never', 'ultraguard-security-suite' ) )
            ->addData( 'sig_version',         get_option( 'ultraguard_si_sig_version',  '—' ) )
            ->addData( 'vuln_version',        get_option( 'ultraguard_si_vuln_version', '—' ) )
            ->addData( 'bl_hash',             substr( (string) get_option( 'ultraguard_si_blacklist_hash', '' ), 0, 8 ) ?: '—' )
            ->addData( 'fw_hash',             substr( (string) get_option( 'ultraguard_si_fw_hash', '' ), 0, 8 ) ?: '—' )
            ->addData( 'hta_hash',            substr( (string) get_option( 'ultraguard_si_hta_hash', '' ), 0, 8 ) ?: '—' )
            ->addData( 'settings',            $this->si->get_schedule_snapshot() )
            ->addData( 'scheduled', [
                'blacklist'  => $this->next_run( 'ultraguard_si_pull_blacklist' ),
                'signatures' => $this->next_run( 'ultraguard_si_pull_signatures' ),
                'vulndb'     => $this->next_run( 'ultraguard_si_pull_vulndb' ),
                'fw_rules'   => $this->next_run( 'ultraguard_si_pull_fw_rules' ),
                'htaccess'   => $this->next_run( 'ultraguard_si_pull_htaccess' ),
            ] )
            ->send();
    }

    /**
     * Save per-feed automatic sync settings.
     */
    public function handle_save_settings(): void {
        $this->authorize();

        $settings = [];
        foreach ( ServerCommunication::get_sync_resources() as $resource => $config ) {
            $settings[ $resource ] = [
                'enabled'  => ! empty( $_POST[ 'ultraguard_si_' . $resource . '_enabled' ] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Missing -- boolean check only after authorize().
                'interval' => sanitize_key( wp_unslash( (string) ( $_POST[ 'ultraguard_si_' . $resource . '_interval' ] ?? '' ) )), // phpcs:ignore WordPress.Security.NonceVerification.Missing -- authorize() verifies nonce and capability before reading request data.
            ];
        }

        $snapshot = $this->si->save_schedule_settings( $settings );

        AjaxResponseBuilder::make()
            ->success( __( 'Server Intelligence sync settings saved.', 'ultraguard-security-suite' ) )
            ->addData( 'settings', $snapshot )
            ->addNotification( __( 'Automatic intelligence fetching updated.', 'ultraguard-security-suite' ), 'success', 4000 )
            ->send();
    }

    /**
     * Trigger an immediate pull for a specific resource.
     * POST param: `resource` — one of: blacklist | signatures | vulndb | fw_rules | htaccess
     */
    public function handle_pull_now(): void {
        $this->authorize();

        $resource = sanitize_key( wp_unslash( $_POST['resource'] ?? ''  ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- authorize() verifies nonce and capability before reading request data.
        $allowed  = [ 'blacklist', 'signatures', 'vulndb', 'fw_rules', 'htaccess' ];

        if ( ! in_array( $resource, $allowed, true ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Unknown resource.', 'ultraguard-security-suite' ), 'invalid_resource' )
                ->send();
            return;
        }

        $method_map = [
            'blacklist'  => 'pull_blacklist',
            'signatures' => 'pull_signatures',
            'vulndb'     => 'pull_vulndb',
            'fw_rules'   => 'pull_fw_rules',
            'htaccess'   => 'pull_htaccess',
        ];

        set_time_limit( 60 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- bounded execution window for an intentional long-running admin sync action.

        try {
            $this->si->{$method_map[ $resource ]}();
        } catch ( \Throwable $e ) {
            AjaxResponseBuilder::make()
                /* translators: %s: string */
                ->error( sprintf( __( 'Pull failed: %s', 'ultraguard-security-suite' ), esc_html( $e->getMessage() ) ), 'pull_error' )
                ->send();
            return;
        }

        $label = ucwords( str_replace( '_', ' ', $resource ) );

        AjaxResponseBuilder::make()
            /* translators: %s: string */
            ->success( sprintf( __( '%s sync completed successfully.', 'ultraguard-security-suite' ), $label ) )
            ->addData( 'resource', $resource )
            ->addNotification(
                /* translators: %s: string */
                sprintf( __( '%s updated from server.', 'ultraguard-security-suite' ), $label ),
                'success',
                4000
            )
            ->send();
    }

    /**
     * Trigger all pulls immediately (runs sequentially).
     */
    public function handle_pull_all(): void {
        $this->authorize();

        set_time_limit( 120 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- bounded execution window for an intentional long-running admin sync action.

        $results = [];
        foreach ( [ 'pull_blacklist', 'pull_signatures', 'pull_vulndb', 'pull_fw_rules', 'pull_htaccess' ] as $method ) {
            try {
                $this->si->$method();
                $results[ $method ] = 'ok';
            } catch ( \Throwable $e ) {
                $results[ $method ] = 'error: ' . $e->getMessage();
            }
        }

        AjaxResponseBuilder::make()
            ->success( __( 'All intelligence sources synced.', 'ultraguard-security-suite' ) )
            ->addData( 'results', $results )
            ->addNotification( __( 'All data synced from UltraGuard server.', 'ultraguard-security-suite' ), 'success', 5000 )
            ->send();
    }

    /**
     * Flush the pending push queue immediately (blocking).
     */
    public function handle_flush_queue(): void {
        $this->authorize();

        $queue = (array) get_option( 'ultraguard_push_queue', [] );
        $count = count( $queue );

        if ( $count === 0 ) {
            AjaxResponseBuilder::make()
                ->success( __( 'Push queue is already empty.', 'ultraguard-security-suite' ) )
                ->addData( 'flushed', 0 )
                ->send();
            return;
        }

        $this->si->flush_push_queue( $queue );

        AjaxResponseBuilder::make()
            ->success( sprintf(
                /* translators: %d: number */
                _n( 'Flushed %d queued event.', 'Flushed %d queued events.', $count, 'ultraguard-security-suite' ),
                $count
            ) )
            ->addData( 'flushed', $count )
            ->addNotification(
                /* translators: %d: integer */
                sprintf( __( '%d events sent to server.', 'ultraguard-security-suite' ), $count ),
                'success', 3500
            )
            ->send();
    }

    /**
     * Discard all queued push events (for debugging / cleanup).
     */
    public function handle_clear_queue(): void {
        $this->authorize();

        $count = count( (array) get_option( 'ultraguard_push_queue', [] ) );
        update_option( 'ultraguard_push_queue', [], false );

        AjaxResponseBuilder::make()
            ->success( sprintf(
                /* translators: %d: number */
                _n( 'Cleared %d queued event.', 'Cleared %d queued events.', $count, 'ultraguard-security-suite' ),
                $count
            ) )
            ->addData( 'cleared', $count )
            ->addNotification( __( 'Push queue cleared.', 'ultraguard-security-suite' ), 'warning', 3000 )
            ->send();
    }

    /**
     * Return threat IP table statistics.
     */
    public function handle_threat_summary(): void {
        $this->authorize();

        $db_tables = $this->plugin->modules->get_db_tables();
        $threat_t = $db_tables ? $db_tables->get_table( 'threat_ips' ) : null;

        if ( ! $threat_t ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Threat intelligence table not available.', 'ultraguard-security-suite' ), 'no_table' )
                ->send();
            return;
        }

        $summary = $threat_t->get_threat_summary();

        AjaxResponseBuilder::make()
            ->success( __( 'Threat summary retrieved.', 'ultraguard-security-suite' ) )
            ->addData( 'summary', $summary )
            ->send();
    }

    /**
     * Purge expired threat IPs from the table.
     */
    public function handle_purge_old_ips(): void {
        $this->authorize();

        $db_tables = $this->plugin->modules->get_db_tables();
        $threat_t = $db_tables ? $db_tables->get_table( 'threat_ips' ) : null;
        $purged   = 0;

        if ( $threat_t && method_exists( $threat_t, 'cleanup_expired' ) ) {
            $purged = $threat_t->cleanup_expired();
        }

        AjaxResponseBuilder::make()
            ->success( sprintf(
                /* translators: %d: number */
                _n( 'Purged %d expired entry.', 'Purged %d expired entries.', $purged, 'ultraguard-security-suite' ),
                $purged
            ) )
            ->addData( 'purged', $purged )
            ->addNotification(
                /* translators: %d: integer */
                sprintf( __( '%d expired threat IPs removed.', 'ultraguard-security-suite' ), $purged ),
                'success', 3000
            )
            ->send();
    }

    /**
     * Send a test push event to verify server connectivity.
     */
    public function handle_test_push(): void {
        $this->authorize();

        // Push a low-priority test event (server ignores event_type='connectivity_test')
        $this->si->push_event( 'connectivity_test', [
            'test'         => true,
            'initiated_by' => get_current_user_id(),
            'threat_level' => 'none',
        ] );

        AjaxResponseBuilder::make()
            ->success( __( 'Test push event queued. Check server logs to confirm receipt.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Push test sent to UltraGuard server.', 'ultraguard-security-suite' ), 'success', 4000 )
            ->send();
    }

    /**
     * Test pull connectivity by requesting the manifest endpoint.
     */
    public function handle_test_pull(): void {
        $this->authorize();

        $params = [
            'site_url'    => home_url(),
            'is_pro'      => LicenseInfo::instance()->isPro() ? '1' : '0',
            'plugin_ver'  => defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '',
            'ts'          => time(),
        ];

        $license_data  = get_option( LicenseInfo::OPTION_DATA, [] );
        $identity_key  = (string) get_option( LicenseInfo::OPTION_KEY, '' );
        $transport_key = (
            is_array( $license_data )
            && ! empty( $license_data['transport_key'] )
            && '' !== $identity_key
            && hash( 'sha256', $identity_key ) === (string) ( $license_data['transport_identity_fingerprint'] ?? '' )
        )
            ? (string) $license_data['transport_key']
            : (string) get_option( LicenseInfo::OPTION_KEY, '' );
        if ( '' === $transport_key ) {
            $transport_key = 'ultraguard-public';
        }
        if ( '' === $identity_key ) {
            $identity_key = 'ultraguard-public';
        }

        $start   = microtime( true );
        $result  = [ 'success' => false, 'message' => 'Unknown error' ];
        $used_url = '';

        foreach ( ServerCommunication::endpointUrlCandidates( ServerCommunication::EP_PULL_MANIFEST ) as $base_url ) {
            $url     = add_query_arg( $params, $base_url );
            $used_url = $url;
            $result  = SecureRemoteTransport::signed_request(
                'GET',
                $url,
                ServerCommunication::EP_PULL_MANIFEST,
                [],
                $transport_key,
                home_url(),
                'UltraGuardPro/' . ( defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '1.0' ),
                [
                    'timeout'   => 15,
                    'sslverify' => ServerCommunication::shouldVerifySslForUrl( $url ),
                ],
                $identity_key
            );

            if ( ! empty( $result['success'] ) ) {
                break;
            }
        }

        $elapsed = round( ( microtime( true ) - $start ) * 1000 );

        if ( ! $result['success'] || ! is_array( $result['response'] ) ) {
            AjaxResponseBuilder::make()
                ->error(
                    /* translators: %s: string */
                    sprintf( __( 'Connection failed: %s', 'ultraguard-security-suite' ), esc_html( (string) ( $result['message'] ?? 'Unknown error' ) ) ),
                    'connection_error'
                )
                ->addNotification( __( 'Server connection failed. Check your internet connection.', 'ultraguard-security-suite' ), 'error', 6000 )
                ->send();
            return;
        }

        if ( ! SecureRemoteTransport::verify_response( $result['response'], (string) $result['request_id'], $transport_key, false ) ) {
            AjaxResponseBuilder::make()
                ->error( __( 'Server response verification failed.', 'ultraguard-security-suite' ), 'invalid_signature' )
                ->addNotification( __( 'The server responded, but its signature could not be verified.', 'ultraguard-security-suite' ), 'error', 6000 )
                ->send();
            return;
        }

        $code = wp_remote_retrieve_response_code( $result['response'] );
        $body = json_decode( wp_remote_retrieve_body( $result['response'] ), true );

        AjaxResponseBuilder::make()
            ->success( __( 'Server connection successful.', 'ultraguard-security-suite' ) )
            ->addData( 'http_code',     $code )
            ->addData( 'latency_ms',    $elapsed )
            ->addData( 'server_url',    $used_url )
            ->addData( 'server_time',   $body['server_time'] ?? null )
            ->addData( 'api_version',   $body['api_version'] ?? null )
            ->addNotification(
                /* translators: %d: integer */
                sprintf( __( 'Connected to UltraGuard server in %dms.', 'ultraguard-security-suite' ), $elapsed ),
                'success', 4000
            )
            ->send();
    }

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

    /** Verify nonce + capability — dies with JSON error on failure. */
    private function authorize(): void {
        check_ajax_referer( self::NONCE, 'nonce' );

        if ( ! function_exists( '\\UltraguardPro\\ultraguard_current_user_can_manage' ) || ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            AjaxResponseBuilder::make()
                ->permissionError( __( 'Insufficient permissions.', 'ultraguard-security-suite' ) )
                ->send();
        }
    }

    /**
     * Return a human-readable "next run" string for a scheduled cron event.
     *
     * @param  string $hook  WP-Cron hook name.
     * @return string
     */
    private function next_run( string $hook ): string {
        $ts = wp_next_scheduled( $hook );
        if ( ! $ts ) { return __( 'Not scheduled', 'ultraguard-security-suite' ); }
        $diff = $ts - time();
        if ( $diff <= 0 )       { return __( 'Due now', 'ultraguard-security-suite' ); }
        if ( $diff < 60 )       { return __( 'In < 1 min', 'ultraguard-security-suite' ); }
        /* translators: %d: integer */
        if ( $diff < 3600 )     { return sprintf( __( 'In %d min', 'ultraguard-security-suite' ), floor( $diff / 60 ) ); }
        /* translators: %1$d: integer, %2$d: integer */
        if ( $diff < 86400 )    { return sprintf( __( 'In %1$dh %2$dm', 'ultraguard-security-suite' ), floor( $diff / 3600 ), floor( ( $diff % 3600 ) / 60 ) ); }
        return date_i18n( 'M j g:i A', $ts );
    }
}
