<?php
/**
 * WPFort Security - File Hash Cache
 * 
 * Provides functionality to cache file hashes and determine when files have changed,
 * allowing the scan to skip unchanged files for improved performance.
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

/**
 * Initialize the file hash cache database table
 * 
 * @return bool True if table exists/was created, false otherwise
 */
function wpsec_initialize_file_hash_cache() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Debug log for initialization
    wpsec_debug_log('🔍 WPFort DEBUG: Initializing file hash cache table', 'debug');
    
    // Check if table exists
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
    wpsec_debug_log('🔍 WPFort DEBUG: Cache table exists? ' . ($table_exists ? 'YES' : 'NO'), 'debug');
    
    if (!$table_exists) {
        wpsec_debug_log('⚠️ WPFort: File hash cache table does not exist, creating it now', 'warning');
        $charset_collate = $wpdb->get_charset_collate();
        
        $sql = "CREATE TABLE $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            file_path varchar(512) NOT NULL,
            file_hash varchar(32) NOT NULL,
            file_size bigint(20) NOT NULL,
            last_modified bigint(20) NOT NULL,
            last_scanned bigint(20) NOT NULL,
            scan_result text,
            PRIMARY KEY  (id),
            UNIQUE KEY file_path (file_path),
            KEY file_hash (file_hash),
            KEY last_modified (last_modified),
            KEY last_scanned (last_scanned)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        $result = dbDelta($sql);
        
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name) {
            wpsec_debug_log('✅ WPFort: File hash cache table created successfully', 'info');
        } else {
            wpsec_debug_log('🚨 WPFort: Failed to create file hash cache table! Error: ' . $wpdb->last_error, 'error');
        }
    } else {
        // Table exists, make sure it has the correct structure
        $table_check = $wpdb->get_results("DESCRIBE $table_name");
        if (empty($table_check)) {
            wpsec_debug_log('🚨 WPFort: File hash cache table exists but appears to be empty or corrupted!', 'info');
        }
    }
    
    return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
}

/**
 * Check if a file needs to be scanned based on hash and modification time
 * 
 * @param string $file_path Path to the file
 * @param bool $force_deep_scan Whether to force a deep scan (ignore cache)
 * @return bool True if the file should be scanned, false if it can be skipped
 */
function wpsec_should_scan_file($file_path, $force_deep_scan = false) {
    global $wpdb, $wpsec_verbose_logging, $wpsec_nuclear_force_scan;
    
    // THE NUCLEAR OPTION: If deep scan or nuclear flag is set, ALWAYS scan everything
    if ($force_deep_scan || !empty($wpsec_nuclear_force_scan)) {
        wpsec_debug_log("💥 FORCE SCAN: $file_path - completely bypassing cache", 'info');
        return true;
    }
    
    // Add extra debugging - always log the first few files for debugging
    static $debug_count = 0;
    $debug_this_file = ($debug_count < 10);
    $debug_count++;
    
    if ($debug_this_file) {
        wpsec_debug_log("🔍 WPFort DEBUG: Checking if file should be scanned: $file_path", 'debug');
    }
    
    // CRITICAL FIX: Every 10th file should be scanned regardless of cache to ensure security
    static $file_count = 0;
    $file_count++;
    if ($file_count % 10 === 0) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: Random sampling enabled, scanning file despite cache: $file_path", 'debug');
        }
        return true;
    }
    
    // If deep scan is forced, always scan the file
    if ($force_deep_scan) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: Deep scan enabled, not using cache for: $file_path", 'debug');
        }
        return true;
    }
    
    // High-risk file types should always be scanned regardless of cache
    $high_risk_extensions = array('.php', '.phtml', '.php5', '.php7', '.pht', '.js', '.htaccess');
    $file_extension = strtolower(substr($file_path, strrpos($file_path, '.') ?: 0));
    if (in_array($file_extension, $high_risk_extensions)) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: High-risk file type ($file_extension), scanning despite cache: $file_path", 'debug');
        }
        // Scan PHP and JavaScript files more frequently (scan 50% of them regardless of cache)
        if (wp_rand(1, 100) <= 50) {
            return true;
        }
    }
    
    // Get file stats
    $file_stats = @stat($file_path);
    if (!$file_stats) {
        // If we can't get file stats, scan it to be safe
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: Can't get file stats for: $file_path", 'debug');
        }
        return true;
    }
    
    $current_size = $file_stats['size'];
    $current_mtime = $file_stats['mtime'];
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Debug the query to see if table exists
    if ($debug_this_file) {
        $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
        wpsec_debug_log("🔍 WPFort DEBUG: Cache table exists before query? " . ($table_exists ? 'YES' : 'NO'), 'debug');
        
        // Log the query we're about to execute
        $query = $wpdb->prepare("SELECT file_hash, file_size, last_modified, last_scanned FROM $table_name WHERE file_path = %s", $file_path);
        wpsec_debug_log("🔍 WPFort DEBUG: Executing query: $query", 'debug');
    }
    
    $cached_data = $wpdb->get_row($wpdb->prepare(
        "SELECT file_hash, file_size, last_modified, last_scanned FROM $table_name WHERE file_path = %s",
        $file_path
    ));
    
    // If no cached data, scan the file
    if (empty($cached_data)) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: No cache entry found for: $file_path", 'debug');
            // Check last error from database
            wpsec_debug_log("🔍 WPFort DEBUG: Last DB error: " . $wpdb->last_error, 'error');
        }
        return true;
    }
    
    // Check if file size or modification time has changed
    if ($cached_data->file_size != $current_size || $cached_data->last_modified != $current_mtime) {
        if (!empty($wpsec_verbose_logging)) {
            wpsec_debug_log("🔄 WPFort: File changed, scanning: $file_path", 'info');
        }
        return true;
    }
    
    // Check when the file was last scanned - if it's been more than 7 days, scan it again
    $seven_days_ago = time() - (7 * 24 * 60 * 60);
    if ($cached_data->last_scanned < $seven_days_ago) {
        if ($debug_this_file) {
            wpsec_debug_log("🔄 WPFort: File not scanned in over 7 days, scanning: $file_path", 'info');
        }
        return true;
    }
    
    // File hasn't changed, can skip scanning
    if (!empty($wpsec_verbose_logging)) {
        wpsec_debug_log("⏩ WPFort: Skipping unchanged file: $file_path", 'info');
    }
    return false;
}

/**
 * Initialize the file hash cache on plugin load
 */
function wpsec_ensure_file_hash_cache_table() {
    static $initialized = false;
    
    // Only run this once per request
    if ($initialized) {
        return;
    }
    
    $initialized = true;
    wpsec_initialize_file_hash_cache();
}

// Run this on every WordPress init to ensure the table exists
add_action('init', 'wpsec_ensure_file_hash_cache_table');

/**
 * Update the file hash cache after a scan
 * 
 * @param string $file_path Path to the file
 * @param string $file_hash MD5 hash of the file (optional, will be calculated if not provided)
 * @param array $scan_result Scan result data
 */
function wpsec_update_file_hash_cache($file_path, $file_hash = null, $scan_result = null) {
    global $wpdb;
    static $debug_count = 0;
    $debug_this_file = ($debug_count < 10);
    $debug_count++;
    
    if ($debug_this_file) {
        wpsec_debug_log("🔍 WPFort DEBUG: Attempting to update cache for: $file_path", 'debug');
    }
    
    // Skip if file doesn't exist
    if (!file_exists($file_path)) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: File doesn't exist, skipping cache update: $file_path", 'debug');
        }
        return;
    }
    
    // Get file stats
    $file_stats = @stat($file_path);
    if (!$file_stats) {
        if ($debug_this_file) {
            wpsec_debug_log("🔍 WPFort DEBUG: Can't get file stats, skipping cache update: $file_path", 'debug');
        }
        return;
    }
    
    $current_size = $file_stats['size'];
    $current_mtime = $file_stats['mtime'];
    $current_time = time();
    
    // Calculate hash if not provided
    if (empty($file_hash)) {
        $file_hash = @md5_file($file_path);
        if (!$file_hash) {
            return; // Can't calculate hash
        }
    }
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Check if table exists before attempting to update
    if ($debug_this_file) {
        $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
        wpsec_debug_log("🔍 WPFort DEBUG: Cache table exists before update? " . ($table_exists ? 'YES' : 'NO'), 'debug');
        
        if (!$table_exists) {
            wpsec_debug_log("🔍 WPFort DEBUG: Attempting to create cache table on-the-fly", 'debug');
            wpsec_initialize_file_hash_cache();
            // Check again if table was created
            $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
            wpsec_debug_log("🔍 WPFort DEBUG: Cache table creation result: " . ($table_exists ? 'SUCCESS' : 'FAILED'), 'debug');
        }
    }
    
    // Update or insert the record
    $result = $wpdb->replace(
        $table_name,
        array(
            'file_path' => $file_path,
            'file_hash' => $file_hash,
            'file_size' => $current_size,
            'last_modified' => $current_mtime,
            'last_scanned' => $current_time,
            'scan_result' => json_encode($scan_result)
        ),
        array('%s', '%s', '%d', '%d', '%d', '%s')
    );
    
    if ($debug_this_file) {
        // Check result and last error
        if ($result === false) {
            wpsec_debug_log("🔍 WPFort DEBUG: Cache update FAILED for: $file_path", 'debug');
            wpsec_debug_log("🔍 WPFort DEBUG: Last DB error: " . $wpdb->last_error, 'error');
        } else {
            wpsec_debug_log("🔍 WPFort DEBUG: Cache update SUCCESS for: $file_path (Result: $result)", 'debug');
        }
    }
}

/**
 * Purge old entries from the file hash cache
 * 
 * @param int $days_old Purge entries older than this many days
 */
function wpsec_purge_file_hash_cache($days_old = 30) {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    $cutoff_time = time() - (DAY_IN_SECONDS * $days_old);
    
    $wpdb->query($wpdb->prepare(
        "DELETE FROM $table_name WHERE last_scanned < %d",
        $cutoff_time
    ));
    
    $rows_affected = $wpdb->rows_affected;
    if ($rows_affected > 0) {
        wpsec_debug_log("🧹 WPFort: Purged $rows_affected old entries from file hash cache", 'info');
    }
}

/**
 * Get cache statistics for the file hash cache
 *
 * @param bool $include_recent_entries Whether to include recent cache entries (can be expensive on large caches)
 * @return array Cache statistics
 */
function wpsec_get_file_hash_cache_stats($include_recent_entries = false) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Check if table exists first
    if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        return [
            'total_files' => 0,
            'total_size' => 0,
            'oldest_entry' => 0,
            'newest_entry' => 0,
            'age_days' => 0,
            'recent_entries' => [],
            'file_types' => [],
            'cache_status' => 'Table does not exist'
        ];
    }
    
    // Get total files count
    $total_files = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
    
    // Get total size of cached files
    $total_size = $wpdb->get_var("SELECT SUM(file_size) FROM $table_name");
    
    // Get oldest entry
    $oldest_entry = $wpdb->get_var("SELECT MIN(last_scanned) FROM $table_name");
    
    // Get newest entry
    $newest_entry = $wpdb->get_var("SELECT MAX(last_scanned) FROM $table_name");
    
    // Calculate cache age in days
    $age_days = 0;
    if ($oldest_entry > 0) {
        $age_days = round((time() - $oldest_entry) / (60 * 60 * 24), 1);
    }
    
    // Get file type distribution (up to 10 types)
    $file_types = [];
    $file_type_query = $wpdb->get_results(
        "SELECT SUBSTRING_INDEX(file_path, '.', -1) as extension, COUNT(*) as count, SUM(file_size) as total_size 
        FROM $table_name 
        GROUP BY extension 
        ORDER BY count DESC 
        LIMIT 10"
    );
    
    if ($file_type_query) {
        foreach ($file_type_query as $type) {
            $file_types[$type->extension] = [
                'count' => (int)$type->count,
                'size' => (int)$type->total_size
            ];
        }
    }
    
    // Get recent entries (limited to 10 for performance)
    $recent_entries = [];
    if ($include_recent_entries) {
        $recent_query = $wpdb->get_results(
            "SELECT file_path, file_size, last_modified, last_scanned 
            FROM $table_name 
            ORDER BY last_scanned DESC 
            LIMIT 10"
        );
        
        if ($recent_query) {
            foreach ($recent_query as $entry) {
                $recent_entries[] = [
                    'file' => $entry->file_path,
                    'size' => (int)$entry->file_size,
                    'modified' => gmdate('Y-m-d H:i:s', $entry->last_modified),
                    'cached' => gmdate('Y-m-d H:i:s', $entry->last_scanned)
                ];
            }
        }
    }
    
    // Get cache growth rate (entries in the last 5 minutes)
    $recent_count = 0;
    $five_min_ago = time() - (5 * 60);
    $recent_count = $wpdb->get_var(
        $wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE last_scanned > %d", $five_min_ago)
    );
    
    // Determine cache status
    $cache_status = 'inactive';
    if ($total_files > 0) {
        if ($recent_count > 0) {
            $cache_status = 'active (growing)'; 
        } else {
            $cache_status = 'static (not growing)';
        }
    }
    
    return [
        'total_files' => (int)$total_files,
        'total_size' => (int)$total_size,
        'oldest_entry' => (int)$oldest_entry,
        'newest_entry' => (int)$newest_entry,
        'age_days' => $age_days,
        'recent_entries' => $recent_entries,
        'file_types' => $file_types,
        'cache_status' => $cache_status,
        'recent_additions' => (int)$recent_count,
        'cache_rate' => $recent_count > 0 ? round($recent_count / 5, 2) . ' files/sec' : '0 files/sec',
        'last_updated' => time()
    ];
}

/**
 * Remove a specific file from the file hash cache
 * 
 * @param string $file_path Path to the file to remove from cache
 * @return bool Success
 */
function wpsec_remove_from_file_hash_cache($file_path) {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Check if table exists
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
    
    if (!$table_exists) {
        wpsec_debug_log('🔍 WPFort DEBUG: Cannot remove from cache, table does not exist', 'debug');
        return false;
    }
    
    // Debug logging for the first few removals
    static $debug_count = 0;
    $debug_this_file = ($debug_count < 10);
    $debug_count++;
    
    if ($debug_this_file) {
        wpsec_debug_log('💥 WPFort: Removing infected file from cache: ' . $file_path, 'info');
    }
    
    // Delete the file from cache
    $result = $wpdb->delete(
        $table_name,
        ['file_path' => $file_path],
        ['%s']
    );
    
    if ($result === false) {
        wpsec_debug_log('⚠️ WPFort ERROR: Failed to remove file from cache: ' . $wpdb->last_error, 'error');
        return false;
    }
    
    return true;
}

/**
 * Clear the entire file hash cache
 */
function wpsec_clear_file_hash_cache() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    $wpdb->query("TRUNCATE TABLE $table_name");
    
    wpsec_debug_log("🧼 WPFort: File hash cache cleared", 'info');
}

/**
 * Check if a file is cached as safe with hash verification
 * This replaces path-based exclusions with proper hash-based caching
 * 
 * @param string $file_path Path to the file
 * @return bool True if file is cached as safe and hash matches
 */
function wpsec_is_file_cached_as_safe($file_path) {
    global $wpdb, $wpsec_verbose_logging;
    
    // Get current file stats
    $file_stats = @stat($file_path);
    if (!$file_stats) {
        return false; // Can't verify, must scan
    }
    
    $current_size = $file_stats['size'];
    $current_mtime = $file_stats['mtime'];
    
    $table_name = $wpdb->prefix . 'wpsec_file_hash_cache';
    
    // Get cached data
    $cached_data = $wpdb->get_row($wpdb->prepare(
        "SELECT file_hash, file_size, last_modified, last_scanned, scan_result FROM $table_name WHERE file_path = %s",
        $file_path
    ));
    
    // If no cached data, file is not cached as safe
    if (empty($cached_data)) {
        return false;
    }
    
    // Check if file size or modification time has changed
    if ($cached_data->file_size != $current_size || $cached_data->last_modified != $current_mtime) {
        if (!empty($wpsec_verbose_logging)) {
            wpsec_debug_log("🔄 WPFort: File changed since cache, must rescan: $file_path", 'info');
        }
        return false;
    }
    
    // Verify current hash matches cached hash
    $current_hash = md5_file($file_path);
    if ($current_hash === false || $current_hash !== $cached_data->file_hash) {
        if (!empty($wpsec_verbose_logging)) {
            wpsec_debug_log("🔄 WPFort: File hash changed, must rescan: $file_path", 'info');
        }
        return false;
    }
    
    // Check if file was previously found to be safe (no infections)
    $scan_result = json_decode($cached_data->scan_result, true);
    if (!is_array($scan_result) || !isset($scan_result['safe']) || !$scan_result['safe']) {
        return false; // File was not marked as safe in previous scan
    }
    
    // Check cache age - don't trust cache older than 7 days
    $seven_days_ago = time() - (7 * 24 * 60 * 60);
    if ($cached_data->last_scanned < $seven_days_ago) {
        if (!empty($wpsec_verbose_logging)) {
            wpsec_debug_log("🔄 WPFort: Cache too old, must rescan: $file_path", 'info');
        }
        return false;
    }
    
    // File is cached as safe and hash matches - can skip scanning
    if (!empty($wpsec_verbose_logging)) {
        wpsec_debug_log("✅ WPFort: File cached as safe with verified hash: $file_path", 'info');
    }
    return true;
}
