<?php
/**
 * AuditLogModule — Activity Audit Log module.
 *
 * Captures and stores admin/security events to the audit_logs DB table:
 *   • All UltraGuard settings changes
 *   • Plugin activate / deactivate / update
 *   • Theme switches
 *   • User role changes (privilege escalation detection)
 *   • User create / delete / profile update
 *   • Post / page create, update, delete
 *   • WordPress core updates
 *   • Failed + successful logins (via Login Limiter hook)
 *   • wp-admin file edits
 *   • Option updates for critical WP options
 *
 * FREE module — available to all users.
 *
 * @package UltraguardPro\modules\audit_log
 * @since   6.6.0
 */

namespace UltraguardPro\modules\audit_log;

use UltraguardPro\modules\core\AbstractModule;
use UltraguardPro\core\Utils;

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Audit log module intentionally reads live event rows for admin reporting.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Audit log retention cleanup uses validated internal table identifiers only.

class AuditLogModule extends AbstractModule {

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

    protected function requires_pro(): bool { return false; }

    /**
     * Return log retention days based on license tier.
     * Free plan: 7 days. Pro: 90 days.
     */
    public function get_retention_days(): int {
        return \UltraguardPro\support\LicenseInfo::instance()->isPro() ? 90 : 7;
    }

    /**
     * Delete audit log entries older than the retention window.
     * Hooked into the daily cleanup cron and called on each boot for free users.
     */
    public function enforce_retention(): void {
        global $wpdb;
        $days     = $this->get_retention_days();
        $cutoff   = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );
        $table    = $this->get_table( 'audit_logs' );

        // Guard: table object must exist and the DB table must actually be installed
        if ( ! $table || ! method_exists( $table, 'get_table_name_raw' ) ) {
            return;
        }
        $tbl_name = $table->get_table_name_raw();
        if ( ! $tbl_name ) {
            return;
        }
        $safe_table_name = esc_sql( $tbl_name );

        // Guard: skip silently if the table does not exist yet (first activation)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $exists = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- query uses prepare(); table name is supplied as a placeholder value.
            'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = %s AND table_name = %s LIMIT 1',
            DB_NAME,
            $tbl_name
        ) );
        if ( ! $exists ) {
            return;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- query uses prepare(); interpolated table name is resolved internally.
            "DELETE FROM `{$safe_table_name}` WHERE created_at < %s LIMIT 500",
            $cutoff
        ) );
    }

    public function boot(): void {
        // 1. AJAX handlers
        ( new ajax\AuditLogAjax( $this->plugin ) )->register();

        // 2. Admin page
        add_action( 'ultraguard_register_admin_pages', [ $this, 'register_pages' ], 27, 1 );

        // 3. Register early integration hooks
        add_action( 'ultraguard_pro/integration/audit_event', [ $this, 'on_integration_audit_event' ], 10, 1 );
        add_filter( 'ultraguard_pro/compliance/available_sections', [ $this, 'add_compliance_sections' ] );
        add_filter( 'ultraguard_pro/compliance/collect_data', [ $this, 'add_compliance_data' ], 10, 4 );

        // 4. Register all module-specific event hooks
        add_action( 'init', [ $this, 'register_hooks' ], 20 );

        // 5. Retention enforcement — runs daily via cron, and on every free-user boot.
        // Uses wp_loaded (not init) so DB tables are fully registered before the query runs.
        add_action( 'ultraguard_daily_cleanup', [ $this, 'enforce_retention' ] );
        if ( ! \UltraguardPro\support\LicenseInfo::instance()->isPro() ) {
            add_action( 'wp_loaded', [ $this, 'enforce_retention' ], 99 );
        }

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

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

    public function register_pages( $admin ): void {
        $admin->register_page( [
            'id'              => 'audit-log',
            'page_title'      => __( 'Audit Log', 'ultraguard-security-suite' ),
            'menu_title'      => __( 'Audit Log', 'ultraguard-security-suite' ),
            'capability'      => 'manage_options',
            'scheme_callback' => [ pages\AuditLogPage::class, $this->plugin ],
            'is_parent'       => true,
            'priority'        => 27,
            'group'           => 'Monitoring',
            'icon'            => 'dashicons-list-view',
        ] );
    }

    // ── Hook registration ──────────────────────────────────────────────────────

    public function register_hooks(): void {
        // ── UltraGuard settings changes ──────────────────────────────────────
        add_action( 'ultraguard_pro_setting_updated',        [ $this, 'on_setting_updated' ], 10, 2 );
        add_action( 'ultraguard_options_saved',              [ $this, 'on_options_saved' ], 10, 1 );

        // ── Plugin lifecycle ─────────────────────────────────────────────────
        add_action( 'activated_plugin',                      [ $this, 'on_plugin_activated' ] );
        add_action( 'deactivated_plugin',                    [ $this, 'on_plugin_deactivated' ] );
        add_action( 'upgrader_process_complete',             [ $this, 'on_upgrader_complete' ], 10, 2 );
        add_action( 'delete_plugin',                         [ $this, 'on_plugin_deleted' ] );

        // ── Theme ─────────────────────────────────────────────────────────────
        add_action( 'switch_theme',                          [ $this, 'on_theme_switch' ], 10, 2 );
        add_action( 'after_theme_setup',                     [ $this, 'on_theme_setup' ] );

        // ── User management ──────────────────────────────────────────────────
        add_action( 'user_register',                         [ $this, 'on_user_register' ] );
        add_action( 'delete_user',                           [ $this, 'on_user_delete' ] );
        add_action( 'profile_update',                        [ $this, 'on_profile_update' ], 10, 2 );
        add_action( 'set_user_role',                         [ $this, 'on_role_change' ], 10, 3 );
        add_action( 'add_user_role',                         [ $this, 'on_role_added' ], 10, 2 );
        add_action( 'remove_user_role',                      [ $this, 'on_role_removed' ], 10, 2 );

        // ── Authentication ────────────────────────────────────────────────────
        add_action( 'wp_login',                              [ $this, 'on_login_success' ], 10, 2 );
        add_action( 'wp_login_failed',                       [ $this, 'on_login_failed' ] );
        add_action( 'wp_logout',                             [ $this, 'on_logout' ] );
        add_action( 'wp_authenticate_user',                  [ $this, 'on_authenticate' ], 99, 2 );

        // ── Posts / pages ────────────────────────────────────────────────────
        add_action( 'post_updated',                          [ $this, 'on_post_updated' ], 10, 3 );
        add_action( 'save_post',                             [ $this, 'on_post_save' ], 10, 2 );
        add_action( 'before_delete_post',                    [ $this, 'on_post_delete' ] );
        add_action( 'wp_trash_post',                         [ $this, 'on_post_trash' ] );

        // ── File editing ──────────────────────────────────────────────────────
        add_action( 'load-theme-editor.php',                 [ $this, 'on_file_editor_access' ] );
        add_action( 'load-plugin-editor.php',                [ $this, 'on_file_editor_access' ] );

        // ── Critical WP option changes ────────────────────────────────────────
        $critical_options = [
            'siteurl', 'blogname', 'admin_email', 'blogdescription',
            'users_can_register', 'default_role', 'permalink_structure',
        ];
        foreach ( $critical_options as $opt ) {
            add_action( 'update_option_' . $opt, [ $this, 'on_critical_option_update' ], 10, 3 );
        }

        // ── UltraGuard module state changes ───────────────────────────────────
        add_action( 'ultraguard_pro/module_activated',       [ $this, 'on_module_activated' ] );
        add_action( 'ultraguard_pro/module_deactivated',     [ $this, 'on_module_deactivated' ] );

        // ── Login Limiter events (cross-module via actions) ───────────────────
        add_action( 'ultraguard_login_success',              [ $this, 'on_ultraguard_login_success' ], 10, 2 );
        add_action( 'ultraguard_ip_unlocked',                [ $this, 'on_ip_unlocked' ] );
        add_action( 'ultraguard_all_lockouts_cleared',       [ $this, 'on_all_lockouts_cleared' ] );
    }

    // =========================================================================
    // EVENT HANDLERS
    // =========================================================================

    // ── UltraGuard settings ────────────────────────────────────────────────────

    public function on_setting_updated( string $key, $new_value ): void {
        $this->record( 'setting_updated', 'ultraguard_setting', $key,
            null, [ 'key' => $key, 'value' => $this->safe_value( $new_value ) ] );
    }

    public function on_options_saved( array $options ): void {
        $this->record( 'bulk_settings_saved', 'ultraguard_settings', null,
            null, [ 'count' => count( $options ), 'keys' => array_keys( $options ) ] );
    }

    // ── Plugin lifecycle ──────────────────────────────────────────────────────

    public function on_plugin_activated( string $plugin ): void {
        $this->record( 'plugin_activated', 'plugin', $plugin );
    }

    public function on_plugin_deactivated( string $plugin ): void {
        $this->record( 'plugin_deactivated', 'plugin', $plugin );
    }

    public function on_plugin_deleted( string $plugin ): void {
        $this->record( 'plugin_deleted', 'plugin', $plugin );
    }

    public function on_upgrader_complete( $upgrader, array $data ): void {
        $type    = $data['type']   ?? '';
        $action  = $data['action'] ?? '';
        if ( $action !== 'update' && $action !== 'install' ) { return; }

        $entity  = $type === 'core' ? 'wordpress_core' : $type;
        $plugins = $data['plugins'] ?? $data['themes'] ?? [];
        if ( is_array( $plugins ) ) {
            foreach ( $plugins as $item ) {
                $this->record( $type . '_' . $action, $entity, $item );
            }
        } else {
            $this->record( $type . '_' . $action, $entity, null );
        }
    }

    // ── Theme ─────────────────────────────────────────────────────────────────

    public function on_theme_switch( string $new_name, \WP_Theme $new_theme ): void {
        $old = wp_get_theme();
        $this->record( 'theme_switched', 'theme', $new_theme->get_stylesheet(),
            [ 'theme' => $old->get_stylesheet() ], [ 'theme' => $new_theme->get_stylesheet() ] );
    }

    public function on_theme_setup(): void {
        // Only log when actually in admin (avoid false-positives on frontend)
        if ( did_action( 'after_setup_theme' ) && is_admin() && current_user_can( 'switch_themes' ) ) {
            $this->record( 'theme_setup', 'theme', get_stylesheet() );
        }
    }

    // ── Users ─────────────────────────────────────────────────────────────────

    public function on_user_register( int $user_id ): void {
        $user = get_userdata( $user_id );
        $this->record( 'user_created', 'user', $user_id,
            null, [ 'login' => $user ? $user->user_login : '', 'email' => $user ? $user->user_email : '' ] );
    }

    public function on_user_delete( int $user_id ): void {
        $user = get_userdata( $user_id );
        $this->record( 'user_deleted', 'user', $user_id,
            [ 'login' => $user ? $user->user_login : '', 'roles' => $user ? $user->roles : [] ] );
    }

    public function on_profile_update( int $user_id, \WP_User $old_user_data ): void {
        $new_user = get_userdata( $user_id );
        if ( ! $new_user ) { return; }

        $changed = [];
        if ( $old_user_data->user_email !== $new_user->user_email ) {
            $changed['email'] = [ 'from' => $old_user_data->user_email, 'to' => $new_user->user_email ];
        }
        if ( $old_user_data->display_name !== $new_user->display_name ) {
            $changed['display_name'] = [ 'from' => $old_user_data->display_name, 'to' => $new_user->display_name ];
        }

        if ( $changed ) {
            $this->record( 'user_profile_updated', 'user', $user_id,
                array_column( array_values( $changed ), 'from', array_keys( $changed ) ),
                array_column( array_values( $changed ), 'to', array_keys( $changed ) ) );
        }
    }

    public function on_role_change( string $user_id, string $role, array $old_roles ): void {
        // Flag escalations to admin
        $escalated = in_array( 'administrator', (array) $role, true )
                  && ! in_array( 'administrator', $old_roles, true );

        $this->record(
            $escalated ? 'privilege_escalation' : 'user_role_changed',
            'user',
            $user_id,
            [ 'roles' => $old_roles ],
            [ 'role'  => $role ]
        );
    }

    public function on_role_added( string $user_id, string $role ): void {
        $this->record( 'user_role_added', 'user', $user_id, null, [ 'role' => $role ] );
    }

    public function on_role_removed( string $user_id, string $role ): void {
        $this->record( 'user_role_removed', 'user', $user_id, null, [ 'role' => $role ] );
    }

    // ── Authentication ─────────────────────────────────────────────────────────

    public function on_login_success( string $user_login, \WP_User $user ): void {
        $this->record( 'login_success', 'user', $user->ID,
            null, [ 'login' => $user_login, 'ip' => Utils::get_client_ip() ] );
    }

    public function on_login_failed( string $username ): void {
        $this->record( 'login_failed', 'user', null,
            null, [ 'username' => $username, 'ip' => Utils::get_client_ip() ] );
    }

    public function on_logout(): void {
        $user_id = get_current_user_id();
        if ( $user_id ) {
            $this->record( 'logout', 'user', $user_id,
                null, [ 'ip' => Utils::get_client_ip() ] );
        }
    }

    public function on_authenticate( $user, string $password ) {
        // Just pass through — we only use this to detect programmatic auth attempts if needed
        return $user;
    }

    // ── Posts ─────────────────────────────────────────────────────────────────

    public function on_post_updated( int $post_id, \WP_Post $post_after, \WP_Post $post_before ): void {
        if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; }
        if ( $post_before->post_status === $post_after->post_status
          && $post_before->post_title  === $post_after->post_title ) { return; }

        $this->record( 'post_updated', $post_after->post_type, $post_id,
            [ 'title' => $post_before->post_title, 'status' => $post_before->post_status ],
            [ 'title' => $post_after->post_title,  'status' => $post_after->post_status ] );
    }

    public function on_post_save( int $post_id, \WP_Post $post ): void {
        if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; }
        if ( $post->post_status === 'auto-draft' ) { return; }
        if ( 'publish' !== $post->post_status ) { return; }
        // Only log new posts (no post_modified_gmt set yet beyond creation)
        if ( $post->post_date_gmt === $post->post_modified_gmt ) {
            $this->record( 'post_created', $post->post_type, $post_id,
                null, [ 'title' => $post->post_title, 'status' => $post->post_status ] );
        }
    }

    public function on_post_delete( int $post_id ): void {
        $post = get_post( $post_id );
        if ( $post ) {
            $this->record( 'post_deleted', $post->post_type, $post_id,
                [ 'title' => $post->post_title ] );
        }
    }

    public function on_post_trash( int $post_id ): void {
        $post = get_post( $post_id );
        if ( $post ) {
            $this->record( 'post_trashed', $post->post_type, $post_id,
                [ 'title' => $post->post_title ] );
        }
    }

    // ── File editing ──────────────────────────────────────────────────────────

    public function on_file_editor_access(): void {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only file editor screen context used for audit logging only.
        $file = sanitize_text_field( wp_unslash( $_GET['file'] ?? ''  ) );
        $this->record( 'file_editor_accessed', 'wp_admin', $file ?: null,
            null, [ 'ip' => Utils::get_client_ip() ] );
    }

    // ── Critical WP options ────────────────────────────────────────────────────

    public function on_critical_option_update( $old_value, $new_value, string $option ): void {
        if ( $old_value === $new_value ) { return; }
        $this->record( 'critical_option_updated', 'wp_option', $option,
            [ 'value' => $this->safe_value( $old_value ) ],
            [ 'value' => $this->safe_value( $new_value ) ] );
    }

    // ── UltraGuard modules ─────────────────────────────────────────────────────

    public function on_module_activated( string $module_id ): void {
        $this->record( 'module_activated', 'ultraguard_module', $module_id );
    }

    public function on_module_deactivated( string $module_id ): void {
        $this->record( 'module_deactivated', 'ultraguard_module', $module_id );
    }

    // ── Cross-module from Login Limiter ────────────────────────────────────────

    public function on_ultraguard_login_success( string $ip, \WP_User $user ): void {
        // Only record here if the Login Limiter module itself is NOT active
        // (to avoid duplicate entries — Login Limiter records via its own on_login_success)
        if ( ! $this->plugin->modules->has( 'login_limiter' ) ) {
            $this->record( 'login_success', 'user', $user->ID, null, [ 'ip' => $ip ] );
        }
    }

    public function on_ip_unlocked( string $ip ): void {
        $this->record( 'ip_unlocked', 'ip_address', $ip, null, [ 'ip' => $ip ] );
    }

    public function on_all_lockouts_cleared(): void {
        $this->record( 'all_lockouts_cleared', 'login_limiter', null );
    }

    public function on_integration_audit_event( array $event ): void {
        $source  = sanitize_key( (string) ( $event['source'] ?? 'integration' ) );
        $action  = sanitize_key( (string) ( $event['event'] ?? 'event' ) );
        $context = is_array( $event['context'] ?? null ) ? $event['context'] : [];

        $entity_id = $context['ip_address'] ?? $context['user_id'] ?? $context['cve_id'] ?? $context['module_id'] ?? null;

        $this->record(
            'integration_' . $action,
            'integration_' . $source,
            $entity_id,
            null,
            [
                'message'   => sanitize_text_field( (string) ( $event['message'] ?? '' ) ),
                'severity'  => sanitize_key( (string) ( $event['severity'] ?? 'low' ) ),
                'category'  => sanitize_key( (string) ( $event['category'] ?? 'general' ) ),
                'context'   => $context,
                'timestamp' => sanitize_text_field( (string) ( $event['timestamp'] ?? '' ) ),
            ]
        );
    }

    public function add_compliance_sections( array $sections ): array {
        $sections['audit_log'] = __( 'Audit Log', '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( 'audit_log', $requested, true ) ) {
            return $sections;
        }

        $table = $this->get_table( 'audit_logs' );
        if ( ! $table ) {
            return $sections;
        }

        $recent = method_exists( $table, 'get_recent' ) ? $table->get_recent( 20 ) : $table->get_by( [], 20, 'created_at', 'DESC' );

        $sections['audit_log'] = [
            'title'   => __( 'Audit Log', 'ultraguard-security-suite' ),
            'content' => [
                'summary' => [
                    'total_entries' => (int) $table->count(),
                    'user_events'   => (int) $table->count( [ 'entity_type' => 'user' ] ),
                    'security_events' => (int) $table->count( [ 'entity_type' => 'integration_firewall' ] ),
                ],
                'entries' => array_map( static function( array $row ): array {
                    return [
                        'action'      => $row['action'] ?? '',
                        'entity_type' => $row['entity_type'] ?? '',
                        'entity_id'   => $row['entity_id'] ?? '',
                        'user_id'     => $row['user_id'] ?? '',
                        'ip_address'  => $row['ip_address'] ?? '',
                        'created_at'  => $row['created_at'] ?? '',
                    ];
                }, $recent ),
            ],
        ];

        return $sections;
    }

    // =========================================================================
    // PRIVATE HELPERS
    // =========================================================================

    /**
     * Write to the audit_logs table.
     */
    private function record( string $action, string $entity_type, $entity_id = null,
                              $old_values = null, $new_values = null ): void {
        if ( ! $this->option( 'audit_log_enabled', true ) ) { return; }

        $table = $this->get_table( 'audit_logs' );
        if ( ! $table ) { return; }

        $table->record( $action, $entity_type, $entity_id, $old_values, $new_values );
    }

    /**
     * Sanitize a value for storage — truncate large arrays, mask sensitive keys.
     */
    private function safe_value( $value ): mixed {
        if ( is_array( $value ) ) {
            // Mask any password/secret/key fields
            foreach ( [ 'password', 'secret', 'key', 'token', 'hash' ] as $mask ) {
                foreach ( $value as $k => &$v ) {
                    if ( stripos( (string) $k, $mask ) !== false ) { $v = '***'; }
                }
            }
            unset( $v );
            return $value;
        }
        if ( is_string( $value ) && strlen( $value ) > 200 ) {
            return substr( $value, 0, 200 ) . '…';
        }
        return $value;
    }
}
