<?php
/**
 * FilecheckerAjax — All wp_ajax_{action} handlers for the File Checker module.
 *
 * Security layers applied to EVERY handler:
 *   1. check_ajax_referer()         — CSRF protection
 *   2. current_user_can('manage_options') — privilege check
 *   3. License gate on pro operations  — server-side enforcement
 *
 * Registration: boot() calls (new FilecheckerAjax($plugin))->register().
 * All actions use the same nonce action: 'ultraguard_admin_nonce'.
 *
 * @package UltraguardPro\modules\filechecker\ajax
 * @since   6.5.0
 */

namespace UltraguardPro\modules\filechecker\ajax;

use UltraguardPro\Plugin;
use UltraguardPro\support\LicenseInfo;
use UltraguardPro\modules\filechecker\FilecheckerEngine;
use UltraguardPro\modules\core\ModuleAjaxSettingsTrait;
use UltraguardPro\support\AjaxResponseBuilder;

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

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

class FilecheckerAjax {

    use ModuleAjaxSettingsTrait;

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

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

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

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

    /**
     * Bind all wp_ajax_{action} hooks.
     */
    public function register(): void {
        $actions = [
            // Dashboard — scan controls
            'ultraguard_fc_start_full_scan'   => 'handle_start_full_scan',
            'ultraguard_fc_start_quick_scan'  => 'handle_start_quick_scan',
            'ultraguard_fc_cancel_scan'       => 'handle_cancel_scan',
            'ultraguard_fc_get_scan_progress' => 'handle_get_scan_progress',
            'ultraguard_fc_refresh_stats'     => 'handle_refresh_stats',

            // Changes
            'ultraguard_fc_approve_change'      => 'handle_approve_change',
            'ultraguard_fc_approve_all_changes' => 'handle_approve_all_changes',
            'ultraguard_fc_quarantine_file'     => 'handle_quarantine_file',
            'ultraguard_fc_quarantine_all'      => 'handle_quarantine_all',
            'ultraguard_fc_view_file_details'   => 'handle_view_file_details',
            'ultraguard_fc_compare_hashes'      => 'handle_compare_hashes',
            'ultraguard_fc_refresh_changes'     => 'handle_refresh_changes',

            // Monitoring
            'ultraguard_fc_add_directory'         => 'handle_add_directory',
            'ultraguard_fc_remove_directory'      => 'handle_remove_directory',
            'ultraguard_fc_toggle_monitoring'     => 'handle_toggle_monitoring',
            'ultraguard_fc_refresh_monitoring'    => 'handle_refresh_monitoring',
            'ultraguard_fc_save_monitoring_settings' => 'handle_save_monitoring_settings',

            // Quarantine
            'ultraguard_fc_restore_quarantined'  => 'handle_restore_quarantined',
            'ultraguard_fc_delete_quarantined'   => 'handle_delete_quarantined',
            'ultraguard_fc_restore_all'          => 'handle_restore_all',
            'ultraguard_fc_delete_all'           => 'handle_delete_all',
            'ultraguard_fc_refresh_quarantine'   => 'handle_refresh_quarantine',

            // History
            'ultraguard_fc_export_history'    => 'handle_export_history',
            'ultraguard_fc_clear_history'     => 'handle_clear_history',
            'ultraguard_fc_delete_file_record'=> 'handle_delete_file_record',
            'ultraguard_fc_delete_session'    => 'handle_delete_session',
            'ultraguard_fc_prune_sessions'    => 'handle_prune_sessions',

            // Settings
            'ultraguard_fc_save_settings'     => 'handle_save_settings',
            'ultraguard_fc_run_integrity_check' => 'handle_run_integrity_check',
            'ultraguard_fc_reset_baseline'    => 'handle_reset_baseline',
            'ultraguard_fc_export_report'     => 'handle_export_report',
        ];

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

    // ── Dashboard — scan controls ──────────────────────────────────────────────

    /**
     * Start a full integrity scan via WP cron (+3 s).
     */
    public function handle_start_full_scan(): void {
        $this->verify();
        $this->require_pro( 'file_integrity_scan', 'run a full file integrity scan' );

        // Guard: refuse if a scan is already in progress
        if ( get_transient( 'ultraguard_fc_scan_in_progress' ) ) {
            AjaxResponseBuilder::make()->error( __( 'A scan is already in progress. Please wait for it to complete.', 'ultraguard-security-suite' ) )->addNotification( __( 'A scan is already in progress.', 'ultraguard-security-suite' ), 'warning' )->send();
            return;
        }

        $scan_id = 'fc_full_' . time() . '_' . wp_generate_password( 6, false );
        set_transient( 'ultraguard_fc_scan_in_progress', [
            'session_id' => $scan_id,
            'type'       => 'full',
            'percent'    => 0,
            'status'     => 'queued',
            'started_at' => time(),
        ], 7200 );

        wp_schedule_single_event( time() + 3, 'ultraguard_filechecker_run' );
        // Force cron to fire immediately on low-traffic / localhost installs
        if ( function_exists( 'spawn_cron' ) ) { spawn_cron(); }

        AjaxResponseBuilder::make()
            ->success( __( 'Full scan queued — it will begin in a few seconds.', 'ultraguard-security-suite' ) )
            ->addData( 'scan_id', $scan_id )
            ->addNotification( __( 'Full scan started.', 'ultraguard-security-suite' ), 'info' )
            ->send();
    }

    /**
     * Start a quick core-only scan.
     */
    public function handle_start_quick_scan(): void {
        $this->verify();
        $this->require_pro( 'file_integrity_scan', 'run a quick file scan' );

        if ( get_transient( 'ultraguard_fc_scan_in_progress' ) ) {
            AjaxResponseBuilder::make()->error( __( 'A scan is already in progress. Please wait for it to complete.', 'ultraguard-security-suite' ) )->addNotification( __( 'A scan is already in progress.', 'ultraguard-security-suite' ), 'warning' )->send();
            return;
        }

        $scan_id = 'fc_quick_' . time() . '_' . wp_generate_password( 6, false );
        set_transient( 'ultraguard_fc_scan_in_progress', [
            'session_id' => $scan_id,
            'type'       => 'quick',
            'percent'    => 0,
            'status'     => 'queued',
            'started_at' => time(),
        ], 3600 );

        wp_schedule_single_event( time() + 3, 'ultraguard_filechecker_quick_run' );
        if ( function_exists( 'spawn_cron' ) ) { spawn_cron(); }

        AjaxResponseBuilder::make()
            ->success( __( 'Quick scan queued.', 'ultraguard-security-suite' ) )
            ->addData( 'scan_id', $scan_id )
            ->addNotification( __( 'Quick scan started.', 'ultraguard-security-suite' ), 'info' )
            ->send();
    }

    /**
     * Cancel the in-progress scan.
     */
    public function handle_cancel_scan(): void {
        $this->verify();
        delete_transient( 'ultraguard_fc_scan_in_progress' );
        AjaxResponseBuilder::make()
            ->success( __( 'Scan cancelled.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Scan cancelled.', 'ultraguard-security-suite' ), 'warning' )
            ->send();
    }

    /**
     * Return current scan progress for JS polling.
     */
    public function handle_get_scan_progress(): void {
        $this->verify();

        $progress = get_transient( 'ultraguard_fc_scan_in_progress' );
        if ( $progress ) {
            AjaxResponseBuilder::make()->success()->mergeData( array_merge( (array) $progress, [ 'is_scanning' => true ] ) )->send();
        } else {
            AjaxResponseBuilder::make()->success()->addData( 'is_scanning', false )->addData( 'percent', 100 )->send();
        }
    }

    /**
     * Return fresh stat counts for the dashboard cards.
     */
    public function handle_refresh_stats(): void {
        $this->verify();

        $summary = $this->integrity_table() ? $this->integrity_table()->get_integrity_summary() : [];
        $last    = $this->sessions_table()  ? $this->sessions_table()->get_scan_statistics()   : [];

        AjaxResponseBuilder::make()
            ->success( __( 'Stats refreshed.', 'ultraguard-security-suite' ) )
            ->mergeData( array_merge( (array) $summary, (array) $last ) )
            ->send();
    }

    // ── Changes ────────────────────────────────────────────────────────────────

    /**
     * Mark a single file change as approved.
     */
    public function handle_approve_change(): void {
        $this->verify();
        $id    = absint( $_POST['file_id'] ?? 0 );
        $table = $this->integrity_table();
        if ( $table && $id ) { $table->approve_change( $id ); }
        AjaxResponseBuilder::make()
            ->success( __( 'File change approved.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'File change approved.', 'ultraguard-security-suite' ), 'success' )
            ->addRemove( '#ug-fc-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Approve all current changes (set new baseline for changed files).
     */
    public function handle_approve_all_changes(): void {
        $this->verify();
        $table = $this->integrity_table();
        $count = 0;
        if ( $table ) {
            $rows = $table->get_by( [ 'status IN' => [ 'modified', 'added' ] ], 5000 );
            foreach ( $rows as $row ) {
                $table->approve_change( (int) $row['id'] );
                $count++;
            }
        }
        /* translators: %d: number */
        $msg = sprintf( _n( '%d change approved.', '%d changes approved.', $count, 'ultraguard-security-suite' ), $count );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'approved', $count )
            ->addNotification( $msg, 'success' )
            ->emptyElement( '#ultraguard-fc-changes-table tbody' )
            ->updateText( '#ultraguard-fc-changes-count', '0' )
            ->send();
    }

    /**
     * Quarantine a single file by its integrity record ID.
     */
    public function handle_quarantine_file(): void {
        $this->verify();
        $this->require_pro( 'file_quarantine', 'quarantine a file' );

        $id    = absint( $_POST['file_id'] ?? 0 );
        $table = $this->integrity_table();
        $row   = $table ? $table->get( $id ) : null;

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

        $dest = $this->engine()->quarantine_file( $row['file_path'], 'manual' );

        if ( ! $dest ) {
            AjaxResponseBuilder::make()->error( __( 'Quarantine failed — file may no longer exist.', 'ultraguard-security-suite' ) )->addNotification( __( 'Quarantine failed.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        // Flush caches on both affected tables
        $this->integrity_table()?->clear_all_caches();
        $this->quarantine_table()?->clear_all_caches();

        AjaxResponseBuilder::make()
            ->success( __( 'File quarantined successfully.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'File quarantined successfully.', 'ultraguard-security-suite' ), 'success' )
            ->addRemove( '#ug-fc-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Quarantine ALL suspicious files in bulk.
     */
    public function handle_quarantine_all(): void {
        $this->verify();
        $this->require_pro( 'file_quarantine', 'quarantine all flagged files' );

        $table   = $this->integrity_table();
        $rows    = $table ? $table->get_suspicious( 500 ) : [];
        $success = 0;

        foreach ( $rows as $row ) {
            if ( ! empty( $row['file_path'] ) && $this->engine()->quarantine_file( $row['file_path'], 'bulk' ) ) {
                $success++;
            }
        }

        /* translators: %d: number */
        $msg = sprintf( _n( '%d suspicious file quarantined.', '%d suspicious files quarantined.', $success, 'ultraguard-security-suite' ), $success );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'quarantined', $success )
            ->addNotification( $msg, 'success' )
            ->addReload( 1500 )
            ->send();
    }

    /**
     * Return full details for a single file record.
     */
    public function handle_view_file_details(): void {
        $this->verify();
        $id    = absint( $_POST['file_id'] ?? 0 );
        $table = $this->integrity_table();
        $row   = $table ? $table->get( $id ) : null;

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

        // Append a live hash for comparison
        $path = $row['file_path'] ?? '';
        $row['current_hash']    = ( $path && file_exists( $path ) )
            ? hash_file( 'sha256', $path ) : null;
        $row['file_exists']     = $path && file_exists( $path );
        $row['file_size_human'] = $row['file_size']
            ? size_format( (int) $row['file_size'] ) : '—';

        AjaxResponseBuilder::make()->success()->mergeData( (array) $row )->send();
    }

    /**
     * Compare stored hash vs current live hash for a single file.
     */
    public function handle_compare_hashes(): void {
        $this->verify();
        $id    = absint( $_POST['file_id'] ?? 0 );
        $table = $this->integrity_table();
        $row   = $table ? $table->get( $id ) : null;

        if ( ! $row ) {
            AjaxResponseBuilder::make()->error( __( 'Record not found.', 'ultraguard-security-suite' ) )->addNotification( __( 'Record not found.', 'ultraguard-security-suite' ), 'error' )->send( 404 );
            return;
        }

        $path         = $row['file_path'] ?? '';
        $current_hash = ( $path && file_exists( $path ) ) ? hash_file( 'sha256', $path ) : null;

        $changed = $current_hash && $row['file_hash'] && $current_hash !== $row['file_hash'];
        AjaxResponseBuilder::make()
            ->success()
            ->addData( 'stored_hash',  $row['file_hash'] )
            ->addData( 'current_hash', $current_hash )
            ->addData( 'file_path',    esc_html( $path ) )
            ->addData( 'changed',      $changed )
            ->addNotification( $changed ? __( 'File has been modified!', 'ultraguard-security-suite' ) : __( 'File hash matches.', 'ultraguard-security-suite' ), $changed ? 'warning' : 'success' )
            ->send();
    }

    /**
     * Return fresh changes counts.
     */
    public function handle_refresh_changes(): void {
        $this->verify();
        $table = $this->integrity_table();
        $modified   = $table ? $table->count( [ 'status' => 'modified' ] )  : 0;
        $added      = $table ? $table->count( [ 'status' => 'added' ] )      : 0;
        $suspicious = $table ? $table->count( [ 'status' => 'suspicious' ] ) : 0;
        AjaxResponseBuilder::make()
            ->success( __( 'Changes refreshed.', 'ultraguard-security-suite' ) )
            ->addData( 'modified',   $modified )
            ->addData( 'added',      $added )
            ->addData( 'suspicious', $suspicious )
            ->updateText( '#ultraguard-fc-count-modified',   (string) $modified )
            ->updateText( '#ultraguard-fc-count-added',      (string) $added )
            ->updateText( '#ultraguard-fc-count-suspicious', (string) $suspicious )
            ->send();
    }

    // ── Monitoring ─────────────────────────────────────────────────────────────

    /**
     * Add a custom monitoring directory.
     */
    public function handle_add_directory(): void {
        $this->verify();

        $path = sanitize_text_field( wp_unslash( $_POST['directory'] ?? '' ) );
        if ( ! $path || ! is_dir( $path ) ) {
            AjaxResponseBuilder::make()->error( __( 'Directory not found or not readable.', 'ultraguard-security-suite' ) )->addNotification( __( 'Directory not found or not readable.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        $existing = $this->plugin->settings->get_module_settings( 'filechecker' );
        $dirs     = (array) ( $existing['filechecker_extra_dirs'] ?? [] );
        $dirs[]   = $path;
        $this->plugin->settings->save_module_settings( 'filechecker', [
            'filechecker_extra_dirs' => array_values( array_unique( $dirs ) ),
        ] );

        AjaxResponseBuilder::make()
            ->success( __( 'Directory added to monitoring.', 'ultraguard-security-suite' ) )
            ->addData( 'directory', esc_html( $path ) )
            ->addNotification( __( 'Directory added to monitoring.', 'ultraguard-security-suite' ), 'success' )
            ->addAppend( '#ultraguard-fc-directory-list', '<li class="ultraguard-directory-item" data-path="' . esc_js( $path ) . '">' . esc_html( $path ) . ' <button class="ultraguard-remove-dir button-link" data-path="' . esc_js( $path ) . '">&times;</button></li>' )
            ->send();
    }

    /**
     * Remove a custom monitoring directory.
     */
    public function handle_remove_directory(): void {
        $this->verify();

        $path     = sanitize_text_field( wp_unslash( $_POST['directory'] ?? '' ) );
        $existing = $this->plugin->settings->get_module_settings( 'filechecker' );
        $dirs     = array_values( array_filter(
            (array) ( $existing['filechecker_extra_dirs'] ?? [] ),
            fn( $d ) => $d !== $path
        ) );
        $this->plugin->settings->save_module_settings( 'filechecker', [
            'filechecker_extra_dirs' => $dirs,
        ] );
        AjaxResponseBuilder::make()
            ->success( __( 'Directory removed.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Directory removed.', 'ultraguard-security-suite' ), 'info' )
            ->addRemove( '.ultraguard-directory-item[data-path="' . esc_js( $path ) . '"]', true, 200 )
            ->send();
    }

    /**
     * Toggle global file monitoring on/off.
     */
    public function handle_toggle_monitoring(): void {
        $this->verify();
        $enabled = filter_var( wp_unslash( $_POST['enabled'] ?? '0' ), FILTER_VALIDATE_BOOLEAN );

        $this->plugin->settings->save_module_settings( 'filechecker', [
            'filechecker_enabled' => $enabled,
        ] );

        $msg = $enabled
            ? __( 'File monitoring enabled.', 'ultraguard-security-suite' )
            : __( 'File monitoring disabled.', 'ultraguard-security-suite' );
        $builder = AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'enabled', $enabled )
            ->addNotification( $msg, $enabled ? 'success' : 'info' );
        if ( $enabled ) {
            $builder->addClass( '#ultraguard-fc-monitoring-toggle', 'is-enabled' )
                    ->removeClass( '#ultraguard-fc-monitoring-toggle', 'is-disabled' );
        } else {
            $builder->removeClass( '#ultraguard-fc-monitoring-toggle', 'is-enabled' )
                    ->addClass( '#ultraguard-fc-monitoring-toggle', 'is-disabled' );
        }
        $builder->updateAttribute( '#ultraguard-fc-monitoring-toggle', 'data-enabled', $enabled ? '1' : '0' )
                ->send();
    }

    /**
     * Return fresh monitoring stats.
     */
    public function handle_refresh_monitoring(): void {
        $this->verify();
        $table = $this->integrity_table();
        $total    = $table ? $table->count()                             : 0;
        $clean    = $table ? $table->count( [ 'status' => 'clean' ] )    : 0;
        $modified = $table ? $table->count( [ 'status' => 'modified' ] ) : 0;
        $added    = $table ? $table->count( [ 'status' => 'added' ] )    : 0;
        AjaxResponseBuilder::make()
            ->success( __( 'Monitoring stats refreshed.', 'ultraguard-security-suite' ) )
            ->addData( 'total',    $total )
            ->addData( 'clean',    $clean )
            ->addData( 'modified', $modified )
            ->addData( 'added',    $added )
            ->updateText( '#ultraguard-fc-monitoring-total',    (string) $total )
            ->updateText( '#ultraguard-fc-monitoring-clean',    (string) $clean )
            ->updateText( '#ultraguard-fc-monitoring-modified', (string) $modified )
            ->updateText( '#ultraguard-fc-monitoring-added',    (string) $added )
            ->send();
    }

    /**
     * Save monitoring scope toggles.
     */
    public function handle_save_monitoring_settings(): void {
        $this->verify();

        // Monitoring scope sub-set of the full schema
        $schema = [
            'filechecker_enabled'      => [ 'type' => 'bool' ],
            'filechecker_scan_core'    => [ 'type' => 'bool' ],
            'filechecker_scan_plugins' => [ 'type' => 'bool' ],
            'filechecker_scan_themes'  => [ 'type' => 'bool' ],
            'filechecker_scan_uploads' => [ 'type' => 'bool' ],
            'filechecker_schedule'     => [ 'type' => 'select', 'options' => [ 'hourly', 'twicedaily', 'daily', 'weekly' ], 'default' => 'daily' ],
        ];
        $data = $this->sanitize_fields( (array) wp_unslash( $_POST ), $schema );
        $this->plugin->settings->save_module_settings( 'filechecker', $data );

        AjaxResponseBuilder::make()
            ->success( __( 'Monitoring settings saved.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Saved!', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    // ── Quarantine ─────────────────────────────────────────────────────────────

    /**
     * Restore a quarantined file by fc_quarantine row ID.
     */
    public function handle_restore_quarantined(): void {
        $this->verify();
        $this->require_pro( 'file_restore', 'restore a quarantined file' );

        $id = absint( $_POST['file_id'] ?? 0 );

        if ( ! $id || ! $this->engine()->restore_file( $id ) ) {
            AjaxResponseBuilder::make()->error( __( 'Restore failed — quarantine file may be missing.', 'ultraguard-security-suite' ) )->addNotification( __( 'Restore failed.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        $this->integrity_table()?->clear_all_caches();
        $this->quarantine_table()?->clear_all_caches();

        AjaxResponseBuilder::make()
            ->success( __( 'File restored to original location.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'File restored to original location.', 'ultraguard-security-suite' ), 'success' )
            ->addRemove( '#ug-fc-quarantine-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Permanently delete a quarantined file.
     */
    public function handle_delete_quarantined(): void {
        $this->verify();
        $this->require_pro( 'file_restore', 'permanently delete a quarantined file' );

        $id       = absint( $_POST['file_id'] ?? 0 );
        $q_table  = $this->quarantine_table();
        $row      = $q_table ? $q_table->get( $id ) : null;

        if ( ! $row ) {
            AjaxResponseBuilder::make()->error( __( 'Record not found.', 'ultraguard-security-suite' ) )->addNotification( __( 'Record not found.', 'ultraguard-security-suite' ), 'error' )->send();
            return;
        }

        // Delete physical file
        $qpath = $row['quarantine_path'] ?? '';
        if ( $qpath && file_exists( $qpath ) ) { wp_delete_file( $qpath ); }

        // Mark as deleted in DB (keep audit trail)
        if ( $q_table ) { $q_table->mark_deleted( $id ); }

        AjaxResponseBuilder::make()
            ->success( __( 'File permanently deleted.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'File permanently deleted.', 'ultraguard-security-suite' ), 'success' )
            ->addRemove( '#ug-fc-quarantine-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Restore ALL quarantined files.
     */
    public function handle_restore_all(): void {
        $this->verify();
        $this->require_pro( 'file_restore', 'restore all quarantined files' );

        $q_table = $this->quarantine_table();
        $rows    = $q_table ? $q_table->get_active( 1000 ) : [];
        $success = 0;

        foreach ( $rows as $row ) {
            if ( $this->engine()->restore_file( (int) $row['id'] ) ) { $success++; }
        }

        /* translators: %d: number */
        $msg = sprintf( _n( '%d file restored.', '%d files restored.', $success, 'ultraguard-security-suite' ), $success );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'restored', $success )
            ->addNotification( $msg, 'success' )
            ->emptyElement( '#ultraguard-fc-quarantine-table tbody' )
            ->send();
    }

    /**
     * Permanently delete ALL quarantined files.
     */
    public function handle_delete_all(): void {
        $this->verify();
        $this->require_pro( 'file_restore', 'delete all quarantined files' );

        $q_table = $this->quarantine_table();
        $rows    = $q_table ? $q_table->get_active( 2000 ) : [];
        $deleted = 0;

        foreach ( $rows as $row ) {
            $qpath = $row['quarantine_path'] ?? '';
            if ( $qpath && file_exists( $qpath ) ) { wp_delete_file( $qpath ); }
            if ( $q_table ) { $q_table->mark_deleted( (int) $row['id'] ); }
            $deleted++;
        }

        /* translators: %d: number */
        $msg = sprintf( _n( '%d file permanently deleted.', '%d files permanently deleted.', $deleted, 'ultraguard-security-suite' ), $deleted );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'deleted', $deleted )
            ->addNotification( $msg, 'success' )
            ->emptyElement( '#ultraguard-fc-quarantine-table tbody' )
            ->send();
    }

    /**
     * Return fresh quarantine count.
     */
    public function handle_refresh_quarantine(): void {
        $this->verify();
        $q_table = $this->quarantine_table();
        $summary = $q_table ? $q_table->get_quarantine_summary() : [ 'active' => 0 ];
        AjaxResponseBuilder::make()->success( __( 'Quarantine list refreshed.', 'ultraguard-security-suite' ) )->mergeData( (array) $summary )->send();
    }

    // ── History ────────────────────────────────────────────────────────────────

    /**
     * Export scan history as a CSV and return a download URL.
     */
    public function handle_export_history(): void {
        $this->verify();
        $this->require_pro( 'file_integrity_scan', 'export scan history' );

        $table = $this->integrity_table();
        $csv   = $table ? $table->export_to_csv(
            [],
            [ 'file_path', 'status', 'change_type', 'file_hash', 'file_size', 'is_core_file', 'last_checked_at' ],
            50000
        ) : '';

        $dir  = trailingslashit( wp_upload_dir()['basedir'] ) . 'ultraguard-security-suite/exports/';
        wp_mkdir_p( $dir );

        // Prevent direct listing
        if ( ! file_exists( $dir . 'index.php' ) ) {
            file_put_contents( $dir . 'index.php', '<?php // silence' );
        }

        $filename = 'filechecker-export-' . gmdate( 'Y-m-d-His' ) . '.csv';
        file_put_contents( $dir . $filename, $csv );

        $url = trailingslashit( wp_upload_dir()['baseurl'] ) . 'ultraguard-security-suite/exports/' . $filename;
        AjaxResponseBuilder::make()
            ->success( __( 'Export ready.', 'ultraguard-security-suite' ) )
            ->addData( 'download_url', $url )
            ->addNotification( __( 'Export ready.', 'ultraguard-security-suite' ), 'success' )
            ->download( $url )
            ->send();
    }

    /**
     * Clear all file integrity records (truncate).
     */
    public function handle_clear_history(): void {
        $this->verify();
        $table = $this->integrity_table();
        if ( $table ) { $table->truncate(); }
        AjaxResponseBuilder::make()
            ->success( __( 'File integrity history cleared.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'History cleared.', 'ultraguard-security-suite' ), 'success' )
            ->emptyElement( '#ultraguard-fc-history-table tbody' )
            ->updateText( '#ultraguard-fc-history-count', '0' )
            ->send();
    }

    /**
     * Delete a single file integrity record.
     */
    public function handle_delete_file_record(): void {
        $this->verify();
        $id    = absint( $_POST['file_id'] ?? 0 );
        $table = $this->integrity_table();
        if ( $table && $id ) {
            $table->delete( $id );
        }

        AjaxResponseBuilder::make()
            ->success( __( 'File integrity record deleted.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'File integrity record deleted.', 'ultraguard-security-suite' ), 'info' )
            ->addRemove( '#ug-fc-record-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Delete a single scan session record.
     */
    public function handle_delete_session(): void {
        $this->verify();
        $id    = absint( $_POST['session_id'] ?? 0 );
        $table = $this->sessions_table();
        if ( $table && $id ) { $table->delete( $id ); }
        AjaxResponseBuilder::make()
            ->success( __( 'Session record deleted.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Session record deleted.', 'ultraguard-security-suite' ), 'info' )
            ->addRemove( '#ug-fc-session-row-' . $id, true, 300 )
            ->send();
    }

    /**
     * Prune scan sessions older than N days.
     */
    public function handle_prune_sessions(): void {
        $this->verify();
        $days  = absint( $_POST['days'] ?? 90 );
        $table = $this->sessions_table();
        $count = $table ? $table->prune_old_sessions( $days ) : 0;
        /* translators: %d: number */
        $msg = sprintf( _n( '%d session pruned.', '%d sessions pruned.', $count, 'ultraguard-security-suite' ), $count );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'pruned', $count )
            ->addNotification( $msg, 'info' )
            ->send();
    }

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

    private function field_schema(): array {
        return [
            'filechecker_enabled'             => [ 'type' => 'bool' ],
            'filechecker_scan_core'           => [ 'type' => 'bool' ],
            'filechecker_scan_plugins'        => [ 'type' => 'bool' ],
            'filechecker_scan_themes'         => [ 'type' => 'bool' ],
            'filechecker_scan_uploads'        => [ 'type' => 'bool' ],
            'filechecker_scheduled'           => [ 'type' => 'bool' ],
            'filechecker_compare_remote'      => [ 'type' => 'bool' ],
            'filechecker_malware_scan'        => [ 'type' => 'bool' ],
            'filechecker_notify_modified'     => [ 'type' => 'bool' ],
            'filechecker_notify_added'        => [ 'type' => 'bool' ],
            'filechecker_notify_suspicious'   => [ 'type' => 'bool' ],
            'filechecker_scan_on_update'      => [ 'type' => 'bool' ],
            'filechecker_schedule'           => [ 'type' => 'select', 'options' => [ 'hourly', 'twicedaily', 'daily', 'weekly' ], 'default' => 'daily'  ],
            'filechecker_hash_algorithm'     => [ 'type' => 'select', 'options' => [ 'md5', 'sha1', 'sha256' ],                  'default' => 'sha256' ],
            'filechecker_max_file_size'      => [ 'type' => 'int',      'min' => 1, 'max' => 500, 'default' => 50 ],
            'filechecker_exclude_paths'      => [ 'type' => 'textarea', 'default' => '' ],
            'filechecker_exclude_extensions' => [ 'type' => 'text',     'default' => '' ],
        ];
    }

    public function handle_save_settings(): void {
        $this->verify();
        $this->require_pro( 'file_integrity_scan', 'save file checker settings' );

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

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

        AjaxResponseBuilder::make()
            ->success( __( 'File Checker settings saved.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Settings saved!', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    /**
     * Trigger a full integrity check immediately (via cron, offset 3 s).
     */
    public function handle_run_integrity_check(): void {
        $this->verify();
        $this->require_pro( 'file_integrity_scan', 'run an integrity check' );
        wp_schedule_single_event( time() + 3, 'ultraguard_filechecker_run' );
        AjaxResponseBuilder::make()
            ->success( __( 'Full integrity check scheduled — it will begin in a few seconds.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Scan queued!', 'ultraguard-security-suite' ), 'info' )
            ->send();
    }

    /**
     * Reset the file baseline: mark all non-quarantined files as approved/clean.
     */
    public function handle_reset_baseline(): void {
        $this->verify();
        $this->require_pro( 'baseline_reset', 'reset the file baseline' );

        $table = $this->integrity_table();
        $count = $table ? $table->reset_baseline() : 0;

        AjaxResponseBuilder::make()
            ->success( __( 'Baseline reset — all files are now considered trusted.', 'ultraguard-security-suite' ) )
            ->addData( 'reset', $count )
            ->addNotification( __( 'Baseline reset!', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    /**
     * Export file report CSV (alias of history export).
     */
    public function handle_export_report(): void {
        $this->handle_export_history();
    }

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

    /**
     * Verify nonce and capability. Sends JSON error and 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 );
        }
    }

    /**
     * Soft license gate — sends a friendly upgrade nudge instead of a dead wall.
     * Free users learn what they'd get rather than hitting a blank error.
     *
     * @param string $feature_key  Key from UpgradeManager::features()
     * @param string $action_desc  Short description of what they tried to do
     */
    private function require_pro( string $feature_key = 'file_integrity_scan', string $action_desc = '' ): void {
        if ( ! LicenseInfo::instance()->isPro() ) {
            \UltraguardPro\support\UpgradeManager::soft_gate_response( $feature_key, $action_desc );
        }
    }

    /**
     * Return the FileIntegrityTable instance (or null if unavailable).
     *
     * @return \UltraguardPro\modules\filechecker\db\FileIntegrityTable|null
     */
    private function integrity_table(): ?\UltraguardPro\modules\filechecker\db\FileIntegrityTable {
        $db = $this->plugin->modules->get_db_tables();
        return $db ? $db->get_table( 'file_integrity' ) : null;
    }

    /**
     * @return \UltraguardPro\modules\filechecker\db\ScanSessionsTable|null
     */
    private function sessions_table(): ?\UltraguardPro\modules\filechecker\db\ScanSessionsTable {
        $db = $this->plugin->modules->get_db_tables();
        return $db ? $db->get_table( 'scan_sessions' ) : null;
    }

    /**
     * @return \UltraguardPro\modules\filechecker\db\FcQuarantineTable|null
     */
    private function quarantine_table(): ?\UltraguardPro\modules\filechecker\db\FcQuarantineTable {
        $db = $this->plugin->modules->get_db_tables();
        return $db ? $db->get_table( 'fc_quarantine' ) : null;
    }

    /**
     * Lazily create the scan engine.
     *
     * @return FilecheckerEngine
     */
    private function engine(): FilecheckerEngine {
        static $engine = null;
        if ( null === $engine ) {
            $engine = new FilecheckerEngine( $this->plugin );
        }
        return $engine;
    }
}

// phpcs:enable
