<?php
/**
 * AntivirusModule — Antivirus & malware scanning feature module.
 *
 * @package UltraguardPro\modules\antivirus
 */

namespace UltraguardPro\modules\antivirus;

use UltraguardPro\modules\core\AbstractModule;
use UltraguardPro\support\LicenseInfo;

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

class AntivirusModule extends AbstractModule {

    public function get_id(): string { return 'antivirus'; }

    /**
     * Register our settings prefix immediately so Settings::get_option() can
     * resolve 'antivirus_*' keys before boot() is called (e.g. in is_enabled()).
     */
    public function __construct( \UltraguardPro\Plugin $plugin ) {
        parent::__construct( $plugin );
        $this->plugin->settings->register_module_prefix( 'antivirus_', 'antivirus' );
    }

    /**
     * Gate on license first, then the user-configured toggle.
     */
    public function is_enabled(): bool {
        if ( $this->requires_pro() && ! LicenseInfo::instance()->isPro() ) {
            return false;
        }
        return (bool) $this->option( 'antivirus_enabled', true );
    }

    public function boot(): void {
        // 1. Register DB schema
        $_db_tables = $this->plugin->modules->get_db_tables();
        if ( $_db_tables ) {
            $_db_tables->register_schema( new schema\AntivirusSchema() );
        }

        // 2. Seed signatures on first run and sync updates in the background.
        //    Activation already performs the initial synchronous seed, so normal
        //    page loads should only queue recovery or scheduled refresh events.
        add_action( 'ultraguard_antivirus_seed_signatures', [ $this, 'seed_signatures' ] );
        add_action( 'ultraguard_antivirus_sync_signatures', [ $this, 'seed_signatures' ] );
        add_action( 'init', [ $this, 'maybe_seed_signatures' ], 5 );
        add_action( 'wp_loaded', [ $this, 'maybe_schedule_signature_sync' ] );

        // 3. AJAX handlers via dedicated class
        ( new ajax\AntivirusAjax( $this->plugin ) )->register();

        // 4. Dashboard contributor
        ( new AntivirusDashboardContributor( $this->plugin ) )->register();

        // 5. Admin pages
        add_action( 'ultraguard_register_admin_pages', [ $this, 'register_pages' ], 30, 1 );

        // 6. Cron hooks — scheduled scanning is Pro-only
        add_action( 'ultraguard_antivirus_run_scan',       [ $this, 'process_scan'        ] );
        add_action( 'ultraguard_av_scan_chunk',            [ $this, 'process_scan_chunk'  ] );
        add_action( 'ultraguard_antivirus_run_vuln_scan',  [ $this, 'process_vuln_scan'   ] );

        if ( \UltraguardPro\support\LicenseInfo::instance()->isPro() ) {
            // Pro: scheduled automatic scans
            add_action( 'ultraguard_antivirus_scheduled_scan', [ $this, 'run_scheduled_scan' ] );
            add_action( 'wp_loaded', [ $this, 'maybe_setup_schedule' ] );
            add_action( 'update_option_antivirus_scan_frequency', [ $this, 'reschedule_cron' ], 10, 2 );
            add_action( 'update_option_antivirus_scheduled_scan', [ $this, 'reschedule_cron' ], 10, 2 );
        } else {
            // Free: clear any scheduled scan that may exist (e.g. after downgrade)
            wp_clear_scheduled_hook( 'ultraguard_antivirus_scheduled_scan' );
        }

        // 7. Email notifications
        ( new AntivirusNotifications( $this->plugin ) )->register();

        // 9. Protect quarantine directory
        add_action( 'init', [ $this, 'protect_quarantine_dir' ] );

        // 11. Module-owned assets — JS + CSS loaded only on antivirus pages
        add_action( 'ultraguard_after_enqueue_assets', [ $this, 'enqueue_assets' ] );
        add_filter( 'ultraguard_pro/compliance/available_sections', [ $this, 'add_compliance_sections' ] );
        add_filter( 'ultraguard_pro/compliance/collect_data', [ $this, 'add_compliance_data' ], 10, 4 );

        do_action( 'ultraguard_pro/antivirus/booted', $this );
    }

    // ── Asset enqueue ─────────────────────────────────────────────────────────

    /**
     * Enqueue antivirus-specific CSS and JS only on antivirus admin pages.
     *
     * Hooked on 'ultraguard_after_enqueue_assets' which fires inside
     * Admin::maybe_enqueue_assets() — so the core UltraGuard scripts
     * (ultraguard-core, ultraguard-managers, etc.) are already registered
     * and can safely be used as dependencies.
     *
     * All assets live inside this module's own assets/ subdirectory so the
     * module remains self-contained and portable.
     */
    public function enqueue_assets(): void {
        // Only load on antivirus pages
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen detection.
        $ug_page = sanitize_key( wp_unslash( $_GET['ug_page'] ?? ''  ) );
        $antivirus_pages = [
            'antivirus', 'file-scanner', 'malware-detection',
            'quarantine', 'scanner-settings',
            // 'vulnerability-scanner' removed — now owned by VulnerabilityModule
        ];

        if ( ! in_array( $ug_page, $antivirus_pages, true ) ) {
            // Also load on root UG dashboard page when no ug_page param (antivirus summary shown)
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen detection.
            $page = sanitize_key( wp_unslash( $_GET['page'] ?? ''  ) );
            if ( $page !== 'ultraguard-security-suite-dashboard' || ! empty( $ug_page ) ) {
                return;
            }
        }

        $module_url = plugin_dir_url( __FILE__ );
        $version    = defined( 'ULTRAGUARD_PRO_VERSION' ) ? ULTRAGUARD_PRO_VERSION : '1.0.0';

        // ── CSS ───────────────────────────────────────────────────────────────
        wp_enqueue_style(
            'ultraguard-antivirus-scanner',
            $module_url . 'assets/css/antivirus-scanner.css',
            [ 'ultraguard-theme' ],   // depends on core theme CSS for --ug-* variables
            $version
        );

        // ── JS ────────────────────────────────────────────────────────────────
        wp_enqueue_script(
            'ultraguard-antivirus-page',
            $module_url . 'assets/js/antivirus-page.js',
            [ 'jquery', 'ultraguard-managers', 'ultraguard-realtime' ],  // realtime fires ultraguard:scan:* events
            $version,
            true   // load in footer
        );

        // Pass antivirus-specific i18n strings to the JS
        wp_localize_script(
            'ultraguard-antivirus-page',
            'ultraguardI18n',
            [
                'scanning' => __( 'Running…',  'ultraguard-security-suite' ),
                'complete'  => __( 'Complete',       'ultraguard-security-suite' ),
                'stopped'   => __( 'Stopped',        'ultraguard-security-suite' ),
                'error'     => __( 'Error',          'ultraguard-security-suite' ),
                'idle'      => __( 'Idle',           'ultraguard-security-suite' ),
            ]
        );
    }

    // ── Admin pages ───────────────────────────────────────────────────────────

    /**
     * Register all antivirus admin pages with the UltraGuard page manager.
     *
     * @param mixed $admin  The admin page manager instance.
     */
    public function register_pages( $admin ): void {
        $admin->register_page( [
            'id'              => 'antivirus',
            'page_title'      => __( 'Antivirus Scanner', 'ultraguard-security-suite' ),
            'menu_title'      => __( 'Antivirus', 'ultraguard-security-suite' ),
            'capability'      => 'manage_options',
            'scheme_callback' => [ pages\AntivirusDashboard::class, $this->plugin ],
            'is_parent'       => true,
            'priority'        => 5,
            'group'           => 'Detection',
            'icon'            => 'dashicons-shield',
        ] );

        $subpages = [
            [ 'id' => 'file-scanner',         'title' => __( 'File Scanner',           'ultraguard-security-suite' ), 'menu' => __( 'File Scanner',    'ultraguard-security-suite' ), 'class' => pages\AntivirusFileScanner::class,          'priority' => 6  ],
            [ 'id' => 'malware-detection',     'title' => __( 'Malware Detection',      'ultraguard-security-suite' ), 'menu' => __( 'Detections',      'ultraguard-security-suite' ), 'class' => pages\AntivirusMalwareDetection::class,     'priority' => 7  ],
            [ 'id' => 'quarantine',            'title' => __( 'Quarantine',             'ultraguard-security-suite' ), 'menu' => __( 'Quarantine',      'ultraguard-security-suite' ), 'class' => pages\AntivirusQuarantine::class,           'priority' => 9  ],
            [ 'id' => 'scanner-settings',      'title' => __( 'Scanner Settings',       'ultraguard-security-suite' ), 'menu' => __( 'Settings',        'ultraguard-security-suite' ), 'class' => pages\AntivirusSettings::class,            'priority' => 10 ],
        ];

        foreach ( $subpages as $page ) {
            $admin->register_page( [
                'id'              => $page['id'],
                'page_title'      => $page['title'],
                'menu_title'      => $page['menu'],
                'capability'      => 'manage_options',
                'scheme_callback' => [ $page['class'], $this->plugin ],
                'parent_id'       => 'antivirus',
                'priority'        => $page['priority'],
            'group'           => 'Detection',
            ] );
        }
    }

    public function add_compliance_sections( array $sections ): array {
        $sections['malware_scan_history'] = __( 'Malware Scan History', 'ultraguard-security-suite' );
        return $sections;
    }

    public function add_compliance_data( array $sections, string $type, array $options, array $meta ): array {
        $requested = isset( $options['sections'] ) && is_array( $options['sections'] ) ? $options['sections'] : [];
        if ( $requested && ! in_array( 'malware_scan_history', $requested, true ) ) {
            return $sections;
        }

        $scan_table       = $this->get_table( 'scan_results' );
        $quarantine_table = $this->get_table( 'quarantine' );

        if ( ! $scan_table ) {
            return $sections;
        }

        $stats  = method_exists( $scan_table, 'get_overall_stats' ) ? $scan_table->get_overall_stats() : [
            'total'       => (int) $scan_table->count(),
            'infected'    => (int) $scan_table->count( [ 'status' => 'infected' ] ),
            'clean'       => (int) $scan_table->count( [ 'status' => 'clean' ] ),
            'quarantined' => (int) $scan_table->count( [ 'status' => 'quarantined' ] ),
            'critical'    => (int) $scan_table->count( [ 'threat_level' => 'critical' ] ),
        ];
        $recent = method_exists( $scan_table, 'get_recent' ) ? $scan_table->get_recent( 15 ) : $scan_table->get_by( [], 15, 'created_at', 'DESC' );

        $sections['malware_scan_history'] = [
            'title'   => __( 'Malware Scan History', 'ultraguard-security-suite' ),
            'content' => [
                'summary'         => [
                    'total_results'      => (int) ( $stats['total'] ?? 0 ),
                    'infected_files'     => (int) ( $stats['infected'] ?? 0 ),
                    'critical_findings'  => (int) ( $stats['critical'] ?? 0 ),
                    'quarantine_items'   => $quarantine_table ? (int) $quarantine_table->count() : 0,
                ],
                'recent_findings' => array_map( static function( array $row ): array {
                    return [
                        'file_path'    => $row['file_path'] ?? '',
                        'status'       => $row['status'] ?? '',
                        'threat_type'  => $row['threat_type'] ?? '',
                        'threat_level' => $row['threat_level'] ?? '',
                        'created_at'   => $row['created_at'] ?? '',
                    ];
                }, $recent ),
            ],
        ];

        return $sections;
    }

    // ── Cron processing ───────────────────────────────────────────────────────

    /**
     * Background cron processor for file scans.
     *
     * @param string $scan_id  Unique scan session identifier.
     */
    public function process_scan( string $scan_id ): void {
        $scan_table = $this->get_table( 'scan_results' );
        $sig_table  = $this->get_table( 'malware_signatures' );
        $quar_table = $this->get_table( 'quarantine' );

        if ( ! $scan_table || ! $sig_table || ! $quar_table ) {
            $this->update_scan_meta( $scan_id, [ 'status' => 'error', 'error' => 'DB tables unavailable' ] );
            return;
        }

        $this->process_scan_chunk();
    }

    /**
     * Process a single antivirus scan chunk and reschedule if more work remains.
     */
    public function process_scan_chunk(): void {
        $meta       = get_option( 'ultraguard_active_scan', [] );
        $scan_id    = (string) ( $meta['scan_id'] ?? '' );
        $scan_table = $this->get_table( 'scan_results' );
        $sig_table  = $this->get_table( 'malware_signatures' );
        $quar_table = $this->get_table( 'quarantine' );

        if ( '' === $scan_id || ( $meta['status'] ?? '' ) === 'cancelled' ) {
            return;
        }

        if ( ! $scan_table || ! $sig_table || ! $quar_table ) {
            $this->update_scan_meta( $scan_id, [ 'status' => 'error', 'error' => 'DB tables unavailable' ] );
            return;
        }

        $engine = new AntivirusEngine( $scan_table, $sig_table, $quar_table, $this->build_engine_settings(), $scan_id );
        $result = $engine->process_chunk();
        $state  = $engine->get_progress();
        $total  = (int) ( $state['total'] ?? 0 );
        $done   = ! empty( $result['done'] );
        $pct    = $done ? 100 : ( $total > 0 ? min( 99, (int) floor( ( (int) ( $state['scanned'] ?? 0 ) / $total ) * 100 ) ) : 0 );

        $this->update_scan_meta( $scan_id, [
            'status'        => $done ? 'complete' : 'running',
            'progress'      => $pct,
            'files_scanned' => (int) ( $state['scanned'] ?? 0 ),
            'threats_found' => (int) ( $state['found'] ?? 0 ),
            'current_file'  => (string) ( $state['current_file'] ?? '' ),
            'total_files'   => $total,
            'completed_at'  => $done ? current_time( 'mysql', true ) : null,
        ] );

        if ( $done ) {
            $this->flush_all_antivirus_caches();
            do_action( 'ultraguard_antivirus_scan_completed', $scan_id, [
                'files_scanned' => (int) ( $state['scanned'] ?? 0 ),
                'threats_found' => (int) ( $state['found'] ?? 0 ),
                'files_skipped' => (int) ( $state['files_skipped'] ?? 0 ),
            ] );
            do_action( 'ultraguard_scan_complete' );
            return;
        }

        if ( ! wp_next_scheduled( 'ultraguard_av_scan_chunk' ) ) {
            wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'ultraguard_av_scan_chunk' );
        }
    }

    /**
     * Background cron processor for vulnerability scans.
     */
    public function process_vuln_scan(): void {
        $vuln_table = $this->get_table( 'vulnerability_scans' );
        $sig_table  = $this->get_table( 'malware_signatures' );
        if ( ! $vuln_table || ! $sig_table ) { return; }

        $engine = new AntivirusEngine(
            $this->get_table( 'scan_results' ),
            $sig_table,
            $this->get_table( 'quarantine' ),
            $this->build_engine_settings(),
            'vscan_' . time()
        );

        $result = $engine->run_vulnerability_scan( $vuln_table );

        if ( $result['vulnerabilities_found'] > 0 ) {
            $critical = $vuln_table->get_critical_findings( 5 );
            foreach ( $critical as $vuln ) {
                do_action( 'ultraguard_antivirus_critical_vuln', $vuln['component_slug'], $vuln );
            }
        }

        // Flush caches so the dashboard shows fresh vulnerability counts
        $this->flush_all_antivirus_caches();
    }

    /**
     * Kick off a scheduled quick scan + vulnerability scan via cron.
     */
    public function run_scheduled_scan(): void {
        $scan_id = 'scheduled_' . time();
        update_option( 'ultraguard_active_scan', [
            'scan_id'    => $scan_id,
            'scan_type'  => 'quick',
            'status'     => 'running',
            'started_at' => current_time( 'mysql', true ),
            'progress'   => 0,
        ], false );

        $scan_table = $this->get_table( 'scan_results' );
        $sig_table  = $this->get_table( 'malware_signatures' );
        $quar_table = $this->get_table( 'quarantine' );

        if ( $scan_table && $sig_table && $quar_table ) {
            $engine = new AntivirusEngine( $scan_table, $sig_table, $quar_table, $this->build_engine_settings(), $scan_id );
            $engine->start_chunked_scan( [ 'scan_id' => $scan_id, 'scan_type' => 'quick' ] );
        }

        wp_schedule_single_event( time() + 5, 'ultraguard_av_scan_chunk' );
        wp_schedule_single_event( time() + 30, 'ultraguard_antivirus_run_vuln_scan' );
    }

    // ── Cron scheduling ───────────────────────────────────────────────────────

    /**
     * Register the scheduled scan cron event if not already scheduled.
     */
    public function maybe_setup_schedule(): void {
        if ( ! (bool) $this->option( 'antivirus_scheduled_scan', true ) ) { return; }
        if ( ! wp_next_scheduled( 'ultraguard_antivirus_scheduled_scan' ) ) {
            $frequency = (string) $this->option( 'antivirus_scan_frequency', 'daily' );
            wp_schedule_event( time() + HOUR_IN_SECONDS, $frequency, 'ultraguard_antivirus_scheduled_scan' );
        }
    }

    /**
     * Clear and re-register the cron when frequency or enabled settings change.
     *
     * @param mixed $old  Previous option value.
     * @param mixed $new  New option value.
     */
    public function reschedule_cron( $old = null, $new = null ): void {
        wp_clear_scheduled_hook( 'ultraguard_antivirus_scheduled_scan' );
        if ( (bool) $this->option( 'antivirus_scheduled_scan', true ) ) {
            $frequency = (string) $this->option( 'antivirus_scan_frequency', 'daily' );
            wp_schedule_event( time() + HOUR_IN_SECONDS, $frequency, 'ultraguard_antivirus_scheduled_scan' );
        }
    }

    // ── Signature seeding ─────────────────────────────────────────────────────

    /**
     * Lightweight init hook: if the initial seed marker is missing, queue a
     * one-time recovery seed instead of performing remote-aware checks inline
     * on the current request.
     */
    public function maybe_seed_signatures(): void {
        if ( get_option( SignatureSeeder::SEEDED_AT_OPTION ) ) {
            return;
        }

        if ( ! wp_next_scheduled( 'ultraguard_antivirus_seed_signatures' ) ) {
            wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'ultraguard_antivirus_seed_signatures' );
        }
    }

    /**
     * Keep bundled/remote signature updates on a background schedule so normal
     * admin and frontend requests do not perform remote update checks inline.
     *
     * @return void
     */
    public function maybe_schedule_signature_sync(): void {
        if ( ! wp_next_scheduled( 'ultraguard_antivirus_sync_signatures' ) ) {
            wp_schedule_event( time() + ( 2 * HOUR_IN_SECONDS ), 'twicedaily', 'ultraguard_antivirus_sync_signatures' );
        }
    }

    /**
     * Seed the malware signatures table — public method for:
     *   • The WP-Cron fallback hook 'ultraguard_antivirus_seed_signatures'.
     *   • Direct calls from lifecycle.php at activation time.
     *   • The "Update Signatures" AJAX button in the admin UI.
     *
     * @param  bool $force  When true, forces a re-seed ignoring the version cache.
     * @return array  Seeder result array (inserted, skipped, total, version, error).
     */
    public function seed_signatures( bool $force = false ): array {
        $seeder = new SignatureSeeder();
        return $force ? $seeder->force_reseed() : $seeder->seed();
    }

    // ── Quarantine protection ─────────────────────────────────────────────────

    /**
     * Ensure the quarantine directory has protective files preventing web access.
     */
    public function protect_quarantine_dir(): void {
        $upload_dir     = wp_upload_dir();
        $quarantine_dir = trailingslashit( $upload_dir['basedir'] ) . 'ultraguard-quarantine/';
        if ( ! is_dir( $quarantine_dir ) ) { return; }

        $htaccess = $quarantine_dir . '.htaccess';
        if ( ! file_exists( $htaccess ) ) {
            file_put_contents( $htaccess, "Deny from all\nOptions -Indexes\n" );
        }
        $index = $quarantine_dir . 'index.php';
        if ( ! file_exists( $index ) ) {
            file_put_contents( $index, '<?php // Silence is golden.' );
        }
    }

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

    /**
     * Flush all four antivirus table caches in one call.
     *
     * Called after scan completion, vuln scan, and any operation that mutates
     * multiple tables (quarantine + scan_results, etc.) so the next page load
     * always reads fresh data from the database.
     */
    private function flush_all_antivirus_caches(): void {
        $tables = [ 'scan_results', 'quarantine', 'malware_signatures', 'vulnerability_scans' ];
        foreach ( $tables as $key ) {
            $table = $this->get_table( $key );
            if ( $table ) {
                $table->clear_all_caches();
            }
        }
    }

    /**
     * Build the settings array passed to AntivirusEngine.
     *
     * @return array<string,mixed>
     */
    private function build_engine_settings(): array {
        return [
            'antivirus_scan_php'         => (bool)   $this->option( 'antivirus_scan_php',          true    ),
            'antivirus_scan_js'          => (bool)   $this->option( 'antivirus_scan_js',           true    ),
            'antivirus_scan_html'        => (bool)   $this->option( 'antivirus_scan_html',         true    ),
            'antivirus_scan_uploads'     => (bool)   $this->option( 'antivirus_scan_uploads',      false   ),
            'antivirus_auto_quarantine'  => (bool)   $this->option( 'antivirus_auto_quarantine',   false   ),
            'antivirus_scan_depth'       => (string) $this->option( 'antivirus_scan_depth',        'normal' ),
            'antivirus_max_file_size_mb' => (int)    $this->option( 'antivirus_max_file_size_mb',  10      ),
            'antivirus_exclude_paths'    => (string) $this->option( 'antivirus_exclude_paths',     ''      ),
            'antivirus_wpvulndb_api_key' => (string) $this->option( 'antivirus_wpvulndb_api_key',  ''      ),
        ];
    }

    /**
     * Derive the quarantine destination path for a file.
     *
     * @param  string $original_path  Source file path.
     * @return string                 Quarantine destination.
     */
    public function get_quarantine_path( string $original_path ): string {
        $upload_dir     = wp_upload_dir();
        $quarantine_dir = trailingslashit( $upload_dir['basedir'] ) . 'ultraguard-quarantine/';
        wp_mkdir_p( $quarantine_dir );
        return $quarantine_dir . basename( $original_path ) . '.' . time() . '.quarantine';
    }

    /**
     * Merge update fields into the active scan record.
     *
     * @param string              $scan_id  The scan session ID.
     * @param array<string,mixed> $updates  Fields to merge.
     */
    private function update_scan_meta( string $scan_id, array $updates ): void {
        $meta = get_option( 'ultraguard_active_scan', [] );
        if ( ( $meta['scan_id'] ?? '' ) === $scan_id ) {
            update_option( 'ultraguard_active_scan', array_merge( $meta, $updates ), false );
        }
    }
}
