<?php
defined('ABSPATH') || exit;

/**
 * Activity Log System for WPFort Security Plugin
 * 
 * Provides functionality to log, retrieve, and manage security-related activities
 * on the WordPress site with configurable retention periods and severity levels.
 */

// Define severity levels
define('WPSEC_LOG_INFO', 'info');
define('WPSEC_LOG_WARNING', 'warning');
define('WPSEC_LOG_CRITICAL', 'critical');

/**
 * Initialize the activity log system
 * Creates the required database tables on plugin activation
 */
function wpsec_activity_log_init() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'wpsec_activity_log';
    $charset_collate = $wpdb->get_charset_collate();

    // Check if table exists
    if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
        $sql = "CREATE TABLE $table_name (
            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            timestamp datetime NOT NULL,
            ip_address varchar(45) NOT NULL,
            user_id bigint(20) UNSIGNED DEFAULT NULL,
            username varchar(60) DEFAULT NULL,
            event_type varchar(50) NOT NULL,
            event_context varchar(255) DEFAULT NULL,
            object_type varchar(50) DEFAULT NULL,
            object_id varchar(50) DEFAULT NULL,
            description text NOT NULL,
            severity varchar(10) NOT NULL,
            PRIMARY KEY (id),
            KEY timestamp (timestamp),
            KEY severity (severity),
            KEY event_type (event_type),
            KEY user_id (user_id)
        ) $charset_collate;";

        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }

    // Setup default retention period if not set
    if (!get_option('wpsec_activity_log_retention_days')) {
        update_option('wpsec_activity_log_retention_days', 14);
    }

    // Setup default max entries if not set
    if (!get_option('wpsec_activity_log_max_entries')) {
        update_option('wpsec_activity_log_max_entries', 10000);
    }
}

/**
 * Add an entry to the activity log
 *
 * @param string $event_type      Type of event (login, file_modification, etc.)
 * @param string $description     Description of the event
 * @param string $severity        Severity level (info, warning, critical)
 * @param string $event_context   Optional additional context
 * @param string $object_type     Optional type of object affected (user, plugin, file, etc.)
 * @param string $object_id       Optional ID of object affected
 * @param int    $user_id         Optional user ID (defaults to current user)
 * @param string $username        Optional username (defaults to current user)
 * @param string $ip_address      Optional IP address (defaults to current IP)
 * @return bool                   Whether the log was successfully added
 */
function wpsec_add_to_activity_log($event_type, $description, $severity = WPSEC_LOG_INFO, $event_context = '', $object_type = '', $object_id = '', $user_id = null, $username = '', $ip_address = '') {
    global $wpdb;
    $table_name = $wpdb->prefix . 'wpsec_activity_log';
    
    // Set default user data if not provided
    if ($user_id === null) {
        // Check if wp_get_current_user is available (it's not available very early in WP load)
        if (function_exists('wp_get_current_user')) {
            $current_user = wp_get_current_user();
            $user_id = $current_user->ID;
            $username = $current_user->user_login;
        } else {
            // We're too early in the WordPress load process
            $user_id = 0;
            $username = 'system';
        }
    }
    
    // Get IP address if not provided
    if (empty($ip_address)) {
        $ip_address = wpsec_get_client_ip();
    }
    
    // Insert log entry
    $result = $wpdb->insert(
        $table_name,
        array(
            'timestamp' => current_time('mysql'),
            'ip_address' => $ip_address,
            'user_id' => $user_id,
            'username' => $username,
            'event_type' => $event_type,
            'event_context' => $event_context,
            'object_type' => $object_type,
            'object_id' => $object_id,
            'description' => $description,
            'severity' => $severity
        ),
        array('%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s')
    );
    
    // Check if we need to purge old entries
    wpsec_purge_old_activity_logs();
    
    return $result !== false;
}

/**
 * Get client IP address with proxy support
 *
 * @return string IP address
 */
function wpsec_get_client_ip() {
    $ip_keys = array(
        'HTTP_CLIENT_IP',
        'HTTP_X_FORWARDED_FOR', 
        'HTTP_X_FORWARDED', 
        'HTTP_FORWARDED_FOR', 
        'HTTP_FORWARDED', 
        'REMOTE_ADDR'
    );
    
    foreach ($ip_keys as $key) {
        if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP)) {
            return $_SERVER[$key];
        }
    }
    
    return '0.0.0.0'; // Unknown
}

/**
 * Retrieve activity logs with optional filtering
 *
 * @param array $args Query arguments
 * @return array Activity log entries
 */
function wpsec_get_activity_logs($args = array()) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'wpsec_activity_log';
    
    // Default arguments
    $defaults = array(
        'start_date' => null,
        'end_date' => null,
        'severity' => null,
        'event_type' => null,
        'user_id' => null,
        'username' => null,
        'ip_address' => null,
        'object_type' => null,
        'object_id' => null,
        'per_page' => 100,
        'page' => 1,
        'orderby' => 'timestamp',
        'order' => 'DESC'
    );
    
    // Parse arguments
    $args = wp_parse_args($args, $defaults);
    
    // Build WHERE clause
    $where = array();
    $values = array();
    
    if (!empty($args['start_date'])) {
        // Format date for SQL - add time part if only date is provided
        $start_date = $args['start_date'];
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date)) {
            $start_date .= ' 00:00:00';
        }
        $where[] = 'timestamp >= %s';
        $values[] = $start_date;
    }
    
    if (!empty($args['end_date'])) {
        // Format date for SQL - add time part if only date is provided
        $end_date = $args['end_date'];
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) {
            $end_date .= ' 23:59:59';
        }
        $where[] = 'timestamp <= %s';
        $values[] = $end_date;
    }
    
    if (!empty($args['severity'])) {
        if (is_array($args['severity'])) {
            $placeholders = array_fill(0, count($args['severity']), '%s');
            $where[] = 'severity IN (' . implode(', ', $placeholders) . ')';
            $values = array_merge($values, $args['severity']);
        } else {
            $where[] = 'severity = %s';
            $values[] = $args['severity'];
        }
    }
    
    if (!empty($args['event_type'])) {
        if (is_array($args['event_type'])) {
            $placeholders = array_fill(0, count($args['event_type']), '%s');
            $where[] = 'event_type IN (' . implode(', ', $placeholders) . ')';
            $values = array_merge($values, $args['event_type']);
        } else {
            $where[] = 'event_type = %s';
            $values[] = $args['event_type'];
        }
    }
    
    if (!empty($args['user_id'])) {
        $where[] = 'user_id = %d';
        $values[] = $args['user_id'];
    }
    
    if (!empty($args['username'])) {
        $where[] = 'username = %s';
        $values[] = $args['username'];
    }
    
    if (!empty($args['ip_address'])) {
        $where[] = 'ip_address = %s';
        $values[] = $args['ip_address'];
    }
    
    if (!empty($args['object_type'])) {
        $where[] = 'object_type = %s';
        $values[] = $args['object_type'];
    }
    
    if (!empty($args['object_id'])) {
        $where[] = 'object_id = %s';
        $values[] = $args['object_id'];
    }
    
    // Build query
    $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM $table_name";
    
    if (!empty($where)) {
        $sql .= ' WHERE ' . implode(' AND ', $where);
    }
    
    // Add ORDER BY
    $allowed_orderby = array('id', 'timestamp', 'severity', 'event_type', 'user_id', 'username', 'ip_address');
    $orderby = in_array($args['orderby'], $allowed_orderby) ? $args['orderby'] : 'timestamp';
    $order = strtoupper($args['order']) === 'ASC' ? 'ASC' : 'DESC';
    
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby and $order are validated against allowlists above
    $sql .= " ORDER BY $orderby $order";
    
    // Add pagination
    $per_page = absint($args['per_page']);
    $page = absint($args['page']);
    $offset = ($page - 1) * $per_page;
    
    $sql .= " LIMIT %d OFFSET %d";
    $values[] = $per_page;
    $values[] = $offset;
    
    // Always prepare and execute query
    $results = $wpdb->get_results($wpdb->prepare($sql, $values));
    
    // Get total count - FOUND_ROWS() doesn't need parameters but we use prepare for consistency
    $total = $wpdb->get_var("SELECT FOUND_ROWS()");
    
    return array(
        'items' => $results,
        'total' => (int) $total,
        'pages' => ceil($total / $per_page),
        'page' => $page
    );
}

/**
 * Purge old activity logs based on retention settings
 */
function wpsec_purge_old_activity_logs() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'wpsec_activity_log';
    
    // Get retention settings
    $retention_days = get_option('wpsec_activity_log_retention_days', 14);
    $max_entries = get_option('wpsec_activity_log_max_entries', 10000);
    
    // Delete entries older than retention period
    if ($retention_days > 0) {
        $date = gmdate('Y-m-d H:i:s', strtotime("-$retention_days days"));
        $wpdb->query($wpdb->prepare("DELETE FROM $table_name WHERE timestamp < %s", $date));
    }
    
    // Enforce maximum number of entries
    $total_entries = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
    
    if ($total_entries > $max_entries) {
        $to_delete = $total_entries - $max_entries;
        $wpdb->query("DELETE FROM $table_name ORDER BY timestamp ASC LIMIT $to_delete");
    }
}

/**
 * Update activity log settings
 *
 * @param int $retention_days Number of days to keep logs
 * @param int $max_entries Maximum number of log entries to keep
 * @return bool Whether settings were updated
 */
function wpsec_update_activity_log_settings($retention_days, $max_entries) {
    $updated = true;
    
    if ($retention_days >= 0) {
        $updated = $updated && update_option('wpsec_activity_log_retention_days', absint($retention_days));
    }
    
    if ($max_entries > 0) {
        $updated = $updated && update_option('wpsec_activity_log_max_entries', absint($max_entries));
    }
    
    if ($updated) {
        // Run purge to enforce new settings
        wpsec_purge_old_activity_logs();
    }
    
    return $updated;
}

// Shorthand functions for common log types

/**
 * Log a login attempt
 *
 * @param bool $success Whether login was successful
 * @param string $username Username that attempted login
 * @param string $ip_address IP address of the user
 */
function wpsec_log_login_attempt($success, $username, $ip_address = '') {
    $event_type = 'login_attempt';
    $severity = $success ? WPSEC_LOG_INFO : WPSEC_LOG_WARNING;
    $status = $success ? 'successful' : 'failed';
    $description = sprintf('Login attempt %s for user %s', $status, $username);
    
    wpsec_add_to_activity_log(
        $event_type,
        $description,
        $severity,
        $status,
        'user',
        $username,
        $success ? get_user_by('login', $username)->ID : 0,
        $username,
        $ip_address
    );
}

/**
 * Log user role changes
 *
 * @param int $user_id User ID
 * @param string $old_role Old role
 * @param string $new_role New role
 */
function wpsec_log_role_change($user_id, $old_role, $new_role) {
    $user = get_userdata($user_id);
    $description = sprintf('User role changed from %s to %s for user %s', $old_role, $new_role, $user->user_login);
    
    wpsec_add_to_activity_log(
        'role_change',
        $description,
        WPSEC_LOG_WARNING,
        'role_modification',
        'user',
        $user_id,
        get_current_user_id()
    );
}

/**
 * Log plugin activation/deactivation
 *
 * @param string $plugin Plugin basename
 * @param bool $activated Whether plugin was activated or deactivated
 */
function wpsec_log_plugin_toggle($plugin, $activated) {
    $action = $activated ? 'activated' : 'deactivated';
    $severity = WPSEC_LOG_INFO;
    
    // Increase severity if this is the security plugin itself
    if (strpos($plugin, 'wpfortai-security') !== false) {
        $severity = WPSEC_LOG_WARNING;
    }
    
    wpsec_add_to_activity_log(
        'plugin_' . ($activated ? 'activation' : 'deactivation'),
        sprintf('Plugin %s was %s', $plugin, $action),
        $severity,
        $action,
        'plugin',
        $plugin
    );
}

/**
 * Log file modification
 *
 * @param string $file_path Path to the file
 * @param string $action Action performed (create, modify, delete)
 */
function wpsec_log_file_modification($file_path, $action) {
    $severity = WPSEC_LOG_INFO;
    $relative_path = str_replace(ABSPATH, '', $file_path);
    
    // Set higher severity for sensitive directories
    if (strpos($relative_path, 'wp-admin') === 0 || 
        strpos($relative_path, 'wp-includes') === 0 || 
        $relative_path === 'wp-config.php') {
        $severity = WPSEC_LOG_CRITICAL;
    }
    
    wpsec_add_to_activity_log(
        'file_' . $action,
        sprintf('File %s: %s', $action, $relative_path),
        $severity,
        $action,
        'file',
        $relative_path
    );
}

/**
 * Log security scan events
 *
 * @param string $scan_id Scan ID
 * @param string $action Scan action (started, completed, etc.)
 * @param array $results Optional scan results
 */
function wpsec_log_security_scan($scan_id, $action, $results = array()) {
    $description = sprintf('Security scan %s with ID %s', $action, $scan_id);
    $severity = WPSEC_LOG_INFO;
    
    // If scan found threats, increase severity
    if ($action === 'completed' && !empty($results) && isset($results['infected_count']) && $results['infected_count'] > 0) {
        $description .= sprintf(' - %d threats found', $results['infected_count']);
        $severity = WPSEC_LOG_CRITICAL;
    }
    
    wpsec_add_to_activity_log(
        'security_scan',
        $description,
        $severity,
        $action,
        'scan',
        $scan_id
    );
}

/**
 * Log firewall events
 *
 * @param string $rule_id Rule ID that triggered
 * @param string $ip_address IP address blocked
 * @param string $request_data Request data that triggered the rule
 */
function wpsec_log_firewall_event($rule_id, $ip_address, $request_data) {
    wpsec_add_to_activity_log(
        'firewall_block',
        sprintf('Firewall blocked request from IP %s (Rule: %s)', $ip_address, $rule_id),
        WPSEC_LOG_WARNING,
        'blocked',
        'firewall_rule',
        $rule_id,
        0,
        '',
        $ip_address
    );
}

/**
 * Log system updates
 *
 * @param string $type Update type (core, plugin, theme)
 * @param string $name Name of the updated component
 * @param string $from_version Old version
 * @param string $to_version New version
 */
function wpsec_log_system_update($type, $name, $from_version, $to_version) {
    wpsec_add_to_activity_log(
        $type . '_update',
        sprintf('%s updated: %s from version %s to %s', ucfirst($type), $name, $from_version, $to_version),
        WPSEC_LOG_INFO,
        'update',
        $type,
        $name
    );
}
