<?php
/**
 * NotificationsModule — Pro-tier security alert delivery and notification history.
 *
 * Responsibilities:
 *   • Owns 3 DB tables : notification_log, notification_rules, notification_channels
 *   • Dispatches email / admin-notice / webhook / Slack notifications
 *   • Listens to cross-module security events via do_action hooks only
 *   • Seeds sensible default rules on first activation
 *   • Runs daily cron to prune old log entries
 *   • Provides digest-mode batching for non-critical alerts
 *
 * Event pipeline:
 *   any module fires: do_action('ultraguard_security_event_fired', $type, $severity, $context)
 *     → NotificationsModule::on_security_event()
 *       → NotificationRulesTable::get_matching_rules()
 *         → NotificationsDispatcher::dispatch($rule, $context)
 *           → NotificationLogTable::record()
 *
 * Self-containment:
 *   • Does NOT import or instantiate classes from other modules.
 *   • Accesses other-module data ONLY through apply_filters() / do_action().
 *   • Cross-module events arrive via WordPress hooks only.
 *
 * @package UltraguardPro\modules\notifications
 * @since   6.5.0
 */

namespace UltraguardPro\modules\notifications;

use UltraguardPro\modules\core\AbstractModule;

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

class NotificationsModule extends AbstractModule {

    /** Pro-only module. */
    protected function requires_pro(): bool { return false; }

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

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

    // ── Boot ──────────────────────────────────────────────────────────────────

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

        // 2. AJAX handlers (direct wp_ajax_ hooks)
        ( new ajax\NotificationsAjax( $this->plugin ) )->register();

        // 3. Dashboard contributor
        ( new NotificationsDashboardContributor( $this->plugin ) )->register();

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

        // 5. Central security event listener (any module can fire this)
        add_action( 'ultraguard_security_event_fired',      [ $this, 'on_security_event' ], 10, 3 );

        // 6. Well-known events from specific modules (no direct class dependencies)
        add_action( 'ultraguard_ip_locked_out',             [ $this, 'on_lockout' ],           10, 3 );
        add_action( 'ultraguard_request_blocked',           [ $this, 'on_request_blocked' ],    10, 3 );
        add_action( 'ultraguard_antivirus_scan_completed',  [ $this, 'on_antivirus_complete' ], 10, 1 );
        add_action( 'ultraguard_filechecker_scan_complete', [ $this, 'on_filechecker_complete'],10, 1 );
        add_action( 'ultraguard_filechecker_changes_detected',[ $this,'on_file_changes'],       10, 2 );
        add_action( 'ultraguard_login_success',             [ $this, 'on_login_success' ],      10, 2 );

        // 7. Server Intelligence event hooks (cloud-pushed events)
        add_action( 'ultraguard_si/blacklist_updated',    [ $this, 'on_si_blacklist_updated'   ], 10, 1 );
        add_action( 'ultraguard_si/signatures_updated',   [ $this, 'on_si_signatures_updated'  ], 10, 2 );
        add_action( 'ultraguard_si/vulndb_updated',       [ $this, 'on_si_vulndb_updated'      ], 10, 1 );
        add_action( 'ultraguard_pro/filechecker/suspicious_file', [ $this, 'on_suspicious_file' ], 10, 2 );
        add_action( 'ultraguard_pro/filechecker/file_modified',   [ $this, 'on_core_file_modified' ], 10, 2 );

        // 8. Digest mode cron
        add_action( 'ultraguard_notifications_digest',      [ $this, 'send_digest' ] );
        add_action( 'ultraguard_notifications_cleanup',     [ $this, 'cleanup_old_logs' ] );
        add_action( 'init',                                 [ $this, 'maybe_schedule_crons' ], 30 );

        // 8. Seed default rules after DB upgrade
        add_action( 'ultraguard_pro/db_upgraded',          [ $this, 'maybe_seed_defaults' ], 10, 2 );

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

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

    public function register_pages( $admin ): void {
        $admin->register_page( [
            'id'              => 'notifications',
            'page_title'      => __( 'Notifications', 'ultraguard-security-suite' ),
            'menu_title'      => __( 'Notifications', 'ultraguard-security-suite' ),
            'capability'      => 'manage_options',
            'scheme_callback' => [ pages\NotificationsDashboard::class, $this->plugin ],
            'is_parent'       => true,
            'priority'        => 45,
            'group'           => 'Tools',
            'icon'            => 'dashicons-bell',
        ] );

        $subpages = [
            [ 'id' => 'notifications-rules',     'title' => __( 'Alert Rules',           'ultraguard-security-suite' ), 'menu' => __( 'Alert Rules', 'ultraguard-security-suite' ), 'class' => pages\NotificationsRules::class,      'p' => 46, 'icon' => 'dashicons-filter'        ],
            [ 'id' => 'notifications-channels',  'title' => __( 'Delivery Channels',     'ultraguard-security-suite' ), 'menu' => __( 'Channels',    'ultraguard-security-suite' ), 'class' => pages\NotificationsChannels::class,   'p' => 47, 'icon' => 'dashicons-admin-links'   ],
            [ 'id' => 'notifications-log',       'title' => __( 'Notification Log',      'ultraguard-security-suite' ), 'menu' => __( 'Log',         'ultraguard-security-suite' ), 'class' => pages\NotificationsLog::class,        'p' => 48, 'icon' => 'dashicons-list-view'     ],
            [ 'id' => 'notifications-statistics','title' => __( 'Statistics',            'ultraguard-security-suite' ), 'menu' => __( 'Statistics',  'ultraguard-security-suite' ), 'class' => pages\NotificationsStatistics::class, 'p' => 49, 'icon' => 'dashicons-chart-bar'     ],
            [ 'id' => 'notifications-settings',  'title' => __( 'Notification Settings', 'ultraguard-security-suite' ), 'menu' => __( 'Settings',    'ultraguard-security-suite' ), 'class' => pages\NotificationsSettings::class,  'p' => 50, 'icon' => 'dashicons-admin-settings' ],
        ];

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

    // ── Event listeners ───────────────────────────────────────────────────────

    /**
     * Central dispatcher — fired by do_action('ultraguard_security_event_fired', ...).
     *
     * @param string $event_type
     * @param string $severity    'info' | 'low' | 'medium' | 'high' | 'critical'
     * @param array  $context     Arbitrary key-value pairs for template rendering.
     */
    public function on_security_event( string $event_type, string $severity = 'medium', array $context = [] ): void {
        if ( ! $this->option( 'notifications_enabled', true ) ) { return; }

        $context = array_merge( [
            'event_type' => $event_type,
            'severity'   => $severity,
            'time'       => current_time( 'mysql', true ),
            'ip'         => \UltraguardPro\core\Utils::get_client_ip(),
            'site_name'  => get_bloginfo( 'name' ),
            'site_url'   => home_url(),
        ], $context );

        $rules_t = $this->get_table( 'notification_rules' );
        if ( ! $rules_t ) { return; }

        // Rate limiting: max notifications per hour
        $max_per_hour = (int) $this->option( 'notifications_max_per_hour', 50 );
        if ( $max_per_hour > 0 && $this->is_rate_limited( $max_per_hour ) ) { return; }

        $matching = $rules_t->get_matching_rules( $event_type, $severity );

        foreach ( $matching as $rule ) {
            // Digest mode: batch non-critical alerts
            if ( $this->option( 'notifications_digest_mode', false )
                 && ! in_array( $severity, [ 'critical', 'high' ], true ) ) {
                $this->queue_for_digest( $rule, $context );
                continue;
            }

            $sent = $this->dispatcher()->dispatch( $rule, $context );
            if ( $sent ) {
                $rules_t->mark_triggered( (int) $rule['id'] );
            }
        }
    }

    /** LoginLimiter lockout event. */
    public function on_lockout( string $ip, string $username, int $duration_minutes ): void {
        $this->on_security_event( 'rate_limit_exceeded', 'high', [
            'ip'               => $ip,
            'username'         => $username,
            'lockout_duration' => $duration_minutes,
            'count'            => $duration_minutes,
        ] );
    }

    /** Firewall blocked request event. */
    public function on_request_blocked( string $ip, string $reason, string $threat_level ): void {
        $this->on_security_event( 'request_blocked', $threat_level, [
            'ip'     => $ip,
            'reason' => $reason,
        ] );
    }

    /**
     * Antivirus scan completed — only notify if infected files found.
     * Uses apply_filters to avoid direct access to antivirus tables.
     *
     * @param string $scan_id
     */
    public function on_antivirus_complete( string $scan_id ): void {
        $infected = (int) apply_filters( 'ultraguard_antivirus_infected_count', 0, $scan_id );
        if ( $infected < 1 ) { return; }

        $this->on_security_event( 'malware_detected', 'critical', [
            'scan_id' => $scan_id,
            'count'   => $infected,
        ] );
    }

    /**
     * File Checker scan completed — payload comes via the action context array.
     *
     * @param array $scan_result  Keys: type, session_id, files_scanned, changes_found, etc.
     */
    public function on_filechecker_complete( array $scan_result = [] ): void {
        $changes = (int) ( $scan_result['changes_found'] ?? 0 );
        if ( $changes < 1 ) { return; }

        $this->on_security_event( 'file_modified', 'high', [
            'scan_type'   => $scan_result['type']     ?? 'full',
            'count'       => $changes,
            'modified'    => $scan_result['modified'] ?? 0,
            'suspicious'  => $scan_result['suspicious'] ?? 0,
        ] );
    }

    /**
     * File changes detected (simpler hook, fired even without a full session).
     *
     * @param int $count
     * @param int $session_id
     */
    public function on_file_changes( int $count, int $session_id ): void {
        if ( $count < 1 ) { return; }

        $this->on_security_event( 'file_modified', $count > 5 ? 'high' : 'medium', [
            'count'      => $count,
            'session_id' => $session_id,
        ] );
    }

    /**
     * Successful login — low-priority informational event.
     *
     * @param string $ip
     * @param object $user  WP_User
     */
    public function on_login_success( string $ip, $user ): void {
        if ( ! $this->option( 'notifications_notify_on_login', false ) ) { return; }

        $this->on_security_event( 'admin_login', 'info', [
            'ip'       => $ip,
            'username' => $user->user_login ?? '',
        ] );
    }

    // ── Digest mode ───────────────────────────────────────────────────────────

    /**
     * Queue a notification for digest batching.
     *
     * @param array $rule
     * @param array $context
     */
    private function queue_for_digest( array $rule, array $context ): void {
        $queue   = get_transient( 'ultraguard_notification_digest_queue' ) ?: [];
        $queue[] = [ 'rule' => $rule, 'context' => $context, 'queued_at' => time() ];
        // Limit queue size to prevent transient bloat
        if ( count( $queue ) > 500 ) { $queue = array_slice( $queue, -500 ); }
        set_transient( 'ultraguard_notification_digest_queue', $queue, 7 * DAY_IN_SECONDS );
    }

    /**
     * Send the digest email (called by WP Cron).
     */
    public function send_digest(): void {
        $queue = get_transient( 'ultraguard_notification_digest_queue' );
        if ( ! $queue || ! is_array( $queue ) || empty( $queue ) ) { return; }

        delete_transient( 'ultraguard_notification_digest_queue' );
        $this->dispatcher()->send_digest( $queue );
    }

    // ── Cron ──────────────────────────────────────────────────────────────────

    /**
     * Register recurring cron events on `init` if not already scheduled.
     */
    public function maybe_schedule_crons(): void {
        if ( ! wp_next_scheduled( 'ultraguard_notifications_cleanup' ) ) {
            wp_schedule_event( time() + 3600, 'daily', 'ultraguard_notifications_cleanup' );
        }

        if ( $this->option( 'notifications_digest_mode', false ) ) {
            $freq = $this->option( 'notifications_digest_frequency', 'daily' );
            if ( ! wp_next_scheduled( 'ultraguard_notifications_digest' ) ) {
                wp_schedule_event( time() + 300, $freq, 'ultraguard_notifications_digest' );
            }
        } else {
            wp_clear_scheduled_hook( 'ultraguard_notifications_digest' );
        }
    }

    /**
     * Prune old log entries (called by cron).
     */
    public function cleanup_old_logs(): void {
        $retention = (int) $this->option( 'notifications_log_retention', 90 );
        $log_t     = $this->get_table( 'notification_log' );
        if ( $log_t ) {
            $log_t->delete_older_than( $retention, 'created_at' );
        }
    }

    // ── Seeding ───────────────────────────────────────────────────────────────

    /**
     * Seed sensible default rules on DB upgrade (only if no rules exist).
     *
     * @param string $from
     * @param string $to
     */
    public function maybe_seed_defaults( string $from, string $to ): void {
        $rules_t = $this->get_table( 'notification_rules' );
        if ( $rules_t ) {
            $rules_t->seed_defaults( (string) get_option( 'admin_email' ) );
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    /**
     * Check if we've hit the hourly rate limit for notifications.
     *
     * @param  int $max_per_hour
     * @return bool  true = rate limited (suppress notification)
     */
    private function is_rate_limited( int $max_per_hour ): bool {
        $log_t = $this->get_table( 'notification_log' );
        if ( ! $log_t ) { return false; }

        $since = gmdate( 'Y-m-d H:i:s', time() - 3600 );
        $count = $log_t->count( [ 'created_at >=' => $since ] );
        return $count >= $max_per_hour;
    }

    /**
     * Lazily create and return the dispatcher singleton.
     *
     * @return NotificationsDispatcher
     */
    public function get_dispatcher(): NotificationsDispatcher {
        static $dispatcher = null;
        if ( null === $dispatcher ) {
            $dispatcher = new NotificationsDispatcher( $this->plugin );
        }
        return $dispatcher;
    }

    /** Shortcut for internal use. */
    private function dispatcher(): NotificationsDispatcher {
        return $this->get_dispatcher();
    }

    // =========================================================================
    // SERVER INTELLIGENCE EVENT HANDLERS
    // =========================================================================

    /**
     * Fired when the global threat IP blacklist is updated from the server.
     * Only fires a notification if a significant number of new IPs were added
     * (avoids noise from minor incremental syncs).
     *
     * @param int $count  Number of IPs added or updated.
     */
    public function on_si_blacklist_updated( int $count ): void {
        if ( $count < 100 ) { return; } // Only notify on meaningful bulk updates

        $this->on_security_event( 'blacklist_updated', 'info', [
            'count'      => $count,
            'event_type' => 'blacklist_updated',
            'severity'   => 'info',
            'time'       => current_time( 'mysql', true ),
            'site_name'  => get_bloginfo( 'name' ),
            'site_url'   => home_url(),
            'admin_url'  => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=server-intelligence' ),
            'message'    => sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( '%d threat IP addresses have been added to your local protection database from the UltraGuard cloud.', 'ultraguard-security-suite' ),
                $count
            ),
        ] );
    }

    /**
     * Fired when malware signature patterns are updated from the server.
     *
     * @param int $inserted  New signatures added.
     * @param int $updated   Existing signatures refreshed.
     */
    public function on_si_signatures_updated( int $inserted, int $updated ): void {
        if ( $inserted === 0 && $updated === 0 ) { return; }

        $this->on_security_event( 'signatures_updated', 'info', [
            'inserted'   => $inserted,
            'updated'    => $updated,
            'event_type' => 'signatures_updated',
            'severity'   => 'info',
            'time'       => current_time( 'mysql', true ),
            'site_name'  => get_bloginfo( 'name' ),
            'site_url'   => home_url(),
            'admin_url'  => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=server-intelligence' ),
            'message'    => sprintf(
                /* translators: %1$d: number of newly added signatures, %2$d: number of refreshed signatures. */
                __( 'Malware signature database updated: %1$d new, %2$d refreshed.', 'ultraguard-security-suite' ),
                $inserted,
                $updated
            ),
        ] );
    }

    /**
     * Fired when the vulnerability database is updated from the server.
     *
     * @param int $inserted  New vulnerability entries added.
     */
    public function on_si_vulndb_updated( int $inserted ): void {
        if ( $inserted === 0 ) { return; }

        $this->on_security_event( 'vulndb_updated', 'info', [
            'inserted'   => $inserted,
            'event_type' => 'vulndb_updated',
            'severity'   => 'info',
            'time'       => current_time( 'mysql', true ),
            'site_name'  => get_bloginfo( 'name' ),
            'site_url'   => home_url(),
            'admin_url'  => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=vulnerability-scanner' ),
            'message'    => sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( 'Vulnerability database updated: %d new CVE entries added.', 'ultraguard-security-suite' ),
                $inserted
            ),
        ] );
    }

    /**
     * Fired when the file checker detects a suspicious (potentially malicious) file.
     *
     * @param string $path  Absolute file path.
     * @param array  $info  { file_hash, status, change_type, is_core_file }
     */
    public function on_suspicious_file( string $path, array $info ): void {
        $this->on_security_event( 'suspicious_file', 'high', [
            'file_path'   => wp_basename( $path ),
            'file_ext'    => strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ),
            'change_type' => $info['change_type'] ?? '',
            'is_core'     => ! empty( $info['is_core_file'] ),
            'event_type'  => 'suspicious_file',
            'severity'    => 'high',
            'threat_level'=> 'high',
            'time'        => current_time( 'mysql', true ),
            'site_name'   => get_bloginfo( 'name' ),
            'site_url'    => home_url(),
            'admin_url'   => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=filechecker-changes' ),
            'message'     => sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( 'Suspicious file detected: %s', 'ultraguard-security-suite' ),
                wp_basename( $path )
            ),
        ] );
    }

    /**
     * Fired when the file checker confirms a core WordPress file has been modified
     * (hash mismatch against the official WordPress.org checksum).
     *
     * @param string $path  Absolute file path.
     * @param array  $info  { file_hash, previous_hash, is_core_file, change_type }
     */
    public function on_core_file_modified( string $path, array $info ): void {
        if ( empty( $info['is_core_file'] ) ) { return; }

        $this->on_security_event( 'core_file_modified', 'critical', [
            'file_path'     => ltrim( str_replace( ABSPATH, '', $path ), '/' ),
            'file_ext'      => strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ),
            'previous_hash' => substr( $info['previous_hash'] ?? '', 0, 8 ),
            'current_hash'  => substr( $info['file_hash'] ?? '', 0, 8 ),
            'event_type'    => 'core_file_modified',
            'severity'      => 'critical',
            'threat_level'  => 'critical',
            'time'          => current_time( 'mysql', true ),
            'site_name'     => get_bloginfo( 'name' ),
            'site_url'      => home_url(),
            'admin_url'     => admin_url( 'admin.php?page=ultraguard-security-suite-dashboard&ug_page=filechecker-changes' ),
            'message'       => sprintf(
                /* translators: Placeholder values are inserted into this string. */
                __( 'WordPress core file modified: %s — this may indicate a hack.', 'ultraguard-security-suite' ),
                ltrim( str_replace( ABSPATH, '', $path ), '/' )
            ),
        ] );
    }
}
