<?php
/**
 * Event logger — writes to wp_shieldforge_log, queries it for the dashboard.
 *
 * @package Brainwerk Security Suite
 */

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

class Shieldforge_Logger {

    /** Severity constants. */
    const SEV_INFO     = 1;
    const SEV_WARN     = 2;
    const SEV_HIGH     = 3;
    const SEV_CRITICAL = 4;

    /** Event-type constants — extend as new modules ship. */
    const EVT_LOGIN_OK         = 'login_success';
    const EVT_LOGIN_FAIL       = 'login_failure';
    const EVT_LOGIN_BLOCKED    = 'login_blocked';
    const EVT_BF_LOCKOUT       = 'bruteforce_lockout';
    const EVT_PROBING_404      = 'probing_404';
    const EVT_HARDENING_BLOCK  = 'hardening_block';
    const EVT_USER_REGISTERED  = 'user_registered';
    const EVT_ROLE_CHANGED     = 'role_changed';
    const EVT_ADMIN_CREATED    = 'admin_created';
    const EVT_PLUGIN_ACTIVATED = 'plugin_activated';
    const EVT_PLUGIN_DEACTIV   = 'plugin_deactivated';
    const EVT_THEME_SWITCHED   = 'theme_switched';
    const EVT_OPTION_CHANGED   = 'option_changed';
    const EVT_ANOMALY          = 'anomaly_detected';
    const EVT_FILE_ADDED          = 'file_added';
    const EVT_FILE_CHANGED        = 'file_changed';
    const EVT_FILE_MISSING        = 'file_missing';
    const EVT_MALWARE_SUSPECT     = 'malware_suspect';
    const EVT_VULNERABILITY_FOUND = 'vulnerability_found';
    const EVT_VULNSCAN_DONE       = 'vulnscan_done';
    /* Audit-trail events (Phase 5d). */
    const EVT_LOGOUT               = 'logout';
    const EVT_POST_UPDATED         = 'post_updated';
    const EVT_POST_TRASHED         = 'post_trashed';
    const EVT_POST_DELETED         = 'post_deleted';
    const EVT_USER_DELETED         = 'user_deleted';
    const EVT_USER_PROFILE_UPDATED = 'user_profile_updated';
    const EVT_PLUGIN_UPDATED       = 'plugin_updated';
    const EVT_THEME_UPDATED        = 'theme_updated';
    const EVT_CORE_UPDATED         = 'core_updated';

    /**
     * Persist an event row.
     *
     * @param string $event_type One of the EVT_* constants (or custom).
     * @param array  $args {
     *     @type int    severity   One of SEV_*. Default INFO.
     *     @type string ip         Raw IP. Anonymized if privacy setting on.
     *     @type int    user_id    User ID, if known.
     *     @type string username   Username, if known.
     *     @type array  context    Arbitrary structured data, JSON-encoded.
     * }
     * @return int|false Insert ID or false.
     */
    public static function log( $event_type, array $args = array() ) {
        global $wpdb;

        if ( ! Shieldforge_Settings::get( 'login_log_enabled', true ) && self::is_login_event( $event_type ) ) {
            return false;
        }

        $defaults = array(
            'severity'    => self::SEV_INFO,
            'ip'          => Shieldforge_Utils::get_real_ip(),
            'user_id'     => 0,
            'username'    => '',
            'user_agent'  => Shieldforge_Utils::safe_user_agent(),
            'request_uri' => Shieldforge_Utils::safe_request_uri(),
            'context'     => array(),
        );
        $args = array_merge( $defaults, $args );

        // Stamp every event with the plugin version that produced it.
        // Cheap way to answer "which event came from which release?" later
        // when triaging bug reports across upgraded sites.
        if ( is_array( $args['context'] ) && ! isset( $args['context']['_v'] ) ) {
            $args['context']['_v'] = defined( 'SHIELDFORGE_VERSION' ) ? SHIELDFORGE_VERSION : '0';
        }

        $anonymize = (bool) Shieldforge_Settings::get( 'privacy_anonymize_ip', true );
        $pseudonym = (bool) Shieldforge_Settings::get( 'privacy_pseudonym_users', false );

        $ip      = $args['ip'];
        $ip_anon = Shieldforge_Utils::anonymize_ip( $ip );
        $store_ip = $anonymize ? $ip_anon : $ip;

        $username = (string) $args['username'];
        if ( $pseudonym && '' !== $username ) {
            $username = Shieldforge_Utils::pseudonymize_username( $username );
        }

        $row = array(
            'blog_id'     => Shieldforge_Utils::current_blog_id(),
            'event_type'  => substr( sanitize_key( $event_type ), 0, 64 ),
            'severity'    => max( 1, min( 4, (int) $args['severity'] ) ),
            'ip'          => $store_ip,
            'ip_anon'     => $ip_anon,
            'user_id'     => (int) $args['user_id'],
            'username'    => mb_substr( sanitize_text_field( $username ), 0, 60 ),
            'user_agent'  => $args['user_agent'],
            'request_uri' => $args['request_uri'],
            'context'     => wp_json_encode( $args['context'] ),
            'created_at'  => current_time( 'mysql', 1 ),
        );

        $ok = $wpdb->insert( Shieldforge_Utils::table( 'log' ), $row ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- custom security table (not cacheable, no WP object-cache backing); direct live write by design.
        return $ok ? (int) $wpdb->insert_id : false;
    }

    private static function is_login_event( $event_type ) {
        return in_array( $event_type, array(
            self::EVT_LOGIN_OK,
            self::EVT_LOGIN_FAIL,
            self::EVT_LOGIN_BLOCKED,
        ), true );
    }

    /**
     * Fetch recent events with simple filtering.
     */
    public static function recent( array $filters = array(), $limit = 50, $offset = 0 ) {
        global $wpdb;
        $table = Shieldforge_Utils::table( 'log' );

        $where  = array( '1=1' );
        $params = array();

        if ( ! empty( $filters['event_type'] ) ) {
            $where[]  = 'event_type = %s';
            $params[] = $filters['event_type'];
        }
        if ( ! empty( $filters['ip'] ) ) {
            $where[]  = 'ip = %s';
            $params[] = $filters['ip'];
        }
        if ( ! empty( $filters['since'] ) ) {
            $where[]  = 'created_at >= %s';
            $params[] = $filters['since'];
        }
        if ( isset( $filters['min_severity'] ) ) {
            $where[]  = 'severity >= %d';
            $params[] = (int) $filters['min_severity'];
        }
        if ( isset( $filters['blog_id'] ) ) {
            $where[]  = 'blog_id = %d';
            $params[] = (int) $filters['blog_id'];
        }

        $sql      = "SELECT * FROM {$table} WHERE " . implode( ' AND ', $where ) . ' ORDER BY id DESC LIMIT %d OFFSET %d';
        $params[] = (int) $limit;
        $params[] = (int) $offset;

        return $wpdb->get_results( $wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom security table (not cacheable, no WP object-cache backing); direct live read by design.
    }

    public static function count_since( $since_mysql, array $filters = array() ) {
        global $wpdb;
        $table  = Shieldforge_Utils::table( 'log' );
        $where  = array( 'created_at >= %s' );
        $params = array( $since_mysql );

        if ( ! empty( $filters['event_type'] ) ) {
            $where[]  = 'event_type = %s';
            $params[] = $filters['event_type'];
        }
        if ( ! empty( $filters['ip'] ) ) {
            $where[]  = 'ip = %s';
            $params[] = $filters['ip'];
        }
        if ( ! empty( $filters['username'] ) ) {
            $where[]  = 'username = %s';
            $params[] = $filters['username'];
        }
        if ( isset( $filters['blog_id'] ) ) {
            $where[]  = 'blog_id = %d';
            $params[] = (int) $filters['blog_id'];
        }

        $sql = "SELECT COUNT(*) FROM {$table} WHERE " . implode( ' AND ', $where );
        return (int) $wpdb->get_var( $wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom security table (not cacheable, no WP object-cache backing); direct live read by design.
    }

    public static function top_attacker_ips( $hours = 24, $limit = 10 ) {
        global $wpdb;
        $table = Shieldforge_Utils::table( 'log' );
        $since = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS * (int) $hours );
        $sql   = "SELECT ip, COUNT(*) AS hits FROM {$table}
                  WHERE created_at >= %s AND severity >= %d AND ip <> ''
                  GROUP BY ip ORDER BY hits DESC LIMIT %d";
        return $wpdb->get_results( $wpdb->prepare( $sql, $since, self::SEV_WARN, (int) $limit ) ); // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom security table (not cacheable, no WP object-cache backing); direct live read by design.
    }

    /**
     * Cron-driven cleanup. Removes events older than retention setting.
     */
    public static function cleanup() {
        global $wpdb;
        $days = max( 1, (int) Shieldforge_Settings::get( 'log_retention_days', 30 ) );
        $cutoff = gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS * $days );
        $table = Shieldforge_Utils::table( 'log' );
        return $wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE created_at < %s", $cutoff ) ); // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- custom security table (not cacheable, no WP object-cache backing); direct live write by design.
    }

    public static function register( Shieldforge_Loader $loader ) {
        $loader->add_action( 'shieldforge_cleanup_logs', __CLASS__, 'cleanup' );
    }
}
