<?php

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

/**
 * VMPFence Known File Database System
 * 
 */
class VMPFence_Known_Files {
    
    // File type constants
    const KNOWN_FILE_CORE = 'core';
    const KNOWN_FILE_PLUGIN = 'plugin';  
    const KNOWN_FILE_THEME = 'theme';   
    const KNOWN_FILE_OTHER = 'other';
    
    // Processing constants
    const MAX_QUEUED_RECORDS = 500;
    
    /**
     * @var wpdb
     */
    private $wpdb;
    
    /**
     * @var string
     */
    private $table_name;
    
    /**
     * @var array
     */
    private $known_files = [];
    
    /**
     * @var array
     */
    private $file_records_queue = [];
    
    /**
     * @var bool
     */
    private $initialized = false;
    
    /**
     * @var array
     */
    private $debug_log = [];
    
    public function __construct() {
        global $wpdb;
        $this->wpdb = $wpdb;
        $this->table_name = $wpdb->prefix . 'vmpfence_known_files';
        $this->create_known_files_table();
        
        $this->debug_log('Known Files system initialized');
    }
    
    /**
     * Create the known files database table
     */
    private function create_known_files_table() {
        $charset_collate = $this->wpdb->get_charset_collate();
        
        $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            file_path varchar(500) NOT NULL,
            file_type varchar(20) NOT NULL DEFAULT 'unknown',
            shac binary(32) NOT NULL,
            file_size bigint(20) unsigned NOT NULL DEFAULT 0,
            last_updated datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
            component varchar(100) DEFAULT NULL,
            version varchar(50) DEFAULT NULL,
            is_queued tinyint(1) NOT NULL DEFAULT 0,
            PRIMARY KEY (id),
            UNIQUE KEY file_path_type (file_path, file_type),
            KEY shac_idx (shac),
            KEY file_type_idx (file_type),
            KEY queued_idx (is_queued),
            KEY component_idx (component)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        $result = dbDelta($sql);
        
    }
    
    /**
     * Initialize known files database with WordPress core, plugins, and themes
     */
    public function initialize_known_files() {
        if ($this->initialized) {
            return true;
        }
        
        // CRITICAL FIX: Disable execution time limit entirely for known files indexing
        // This process can take several minutes to hash thousands of files
        @set_time_limit(0); // 0 = unlimited execution time
        @ini_set('max_execution_time', '0');
        
        // Clear existing known files
        $this->clear_known_files();
        
        // Index WordPress core files
        $this->index_core_files();
        
        // Index plugin files
        $this->index_plugin_files();
        
        // Index theme files
        $this->index_theme_files();
        
        // Process any queued records
        $this->process_file_records_queue();
        
        $this->initialized = true;
        return true;
    }
    
    /**
     * Clear all known files from database
     */
    public function clear_known_files() {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property
        $this->wpdb->query("TRUNCATE TABLE {$this->table_name}");
        $this->known_files = [];
        $this->file_records_queue = [];
        $this->debug_log('Known files database cleared');
    }
    
    /**
     * Index WordPress core files SECURELY
     * Only trust files that have official WordPress checksums from wordpress.org
     */
    private function index_core_files() {
        // Get official WordPress checksums from wordpress.org
        $official_checksums = $this->get_wordpress_checksums();
        
        if (empty($official_checksums)) {
            $this->debug_log('WARNING: Cannot verify core files - no official checksums available');
            return;
        }
        
        $verified_files = 0;
        $modified_files = 0;
        $missing_files = 0;
        
        $this->debug_log('Indexing core files using official WordPress checksums (SECURE MODE)...');
        
        // CRITICAL FIX: Extend execution time for large file hashing operations
        // Hashing 2000+ WordPress core files can take several minutes
        @set_time_limit(300); // 5 minutes for core file indexing
        
        // Only add files that exist in official WordPress checksums
        foreach ($official_checksums as $relative_path => $expected_hash) {
            $full_path = ABSPATH . $relative_path;
            
            if (file_exists($full_path)) {
                // Verify the file matches official checksum before trusting it
                $actual_hash = hash_file('md5', $full_path);
                
                if ($actual_hash === $expected_hash) {
                    // File is verified - add to Known Files database
                    $this->add_known_file($full_path, self::KNOWN_FILE_CORE);
                    $verified_files++;
                } else {
                    // File is modified - DO NOT add to Known Files database
                    $modified_files++;
                    $this->debug_log('WARNING: Core file modified (not adding to known files): ' . $relative_path);
                }
            } else {
                // File doesn't exist on disk
                $missing_files++;
            }
        }
        
        $this->debug_log("Indexed {$verified_files} verified core files, rejected {$modified_files} modified files, {$missing_files} missing files");
    }
    
    /**
     * Get official WordPress checksums from wordpress.org API
     */
    private function get_wordpress_checksums() {
        global $wp_version;
        
        // Use WordPress core function if available
        if (function_exists('get_core_checksums')) {
            return get_core_checksums($wp_version, get_locale());
        }
        
        // Fallback: Direct API call to wordpress.org
        $api_url = "https://api.wordpress.org/core/checksums/1.0/?version={$wp_version}";
        $response = wp_remote_get($api_url, ['timeout' => 30]);
        
        if (is_wp_error($response)) {
            $this->debug_log('Failed to fetch WordPress checksums: ' . $response->get_error_message());
            return [];
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (isset($data['checksums']) && is_array($data['checksums'])) {
            $this->debug_log('Retrieved ' . count($data['checksums']) . ' official WordPress checksums');
            return $data['checksums'];
        }
        
        $this->debug_log('No valid checksums found in wordpress.org response');
        return [];
    }
    
    /**
     * Index plugin files SECURELY
     * Only trust plugins from official WordPress.org repository
     */
    private function index_plugin_files() {
        if (!function_exists('get_plugins')) {
            require_once(ABSPATH . 'wp-admin/includes/plugin.php');
        }
        
        $plugins = get_plugins();
        $plugin_dir = WP_PLUGIN_DIR;
        $verified_plugins = 0;
        $custom_plugins = 0;
        
        $this->debug_log('Indexing plugins using WordPress.org verification (SECURE MODE)...');
        
        // CRITICAL FIX: Extend execution time for plugin file indexing
        @set_time_limit(300); // 5 minutes for plugin indexing
        
        foreach ($plugins as $plugin_file => $plugin_data) {
            $plugin_slug = dirname($plugin_file);
            $plugin_name = $plugin_data['Name'];
            
            // Skip plugins with invalid slugs
            if ($plugin_slug === '.') {
                $plugin_slug = basename($plugin_file, '.php');
            }
            
            // Verify if plugin exists in WordPress.org repository
            if ($this->is_wordpress_org_plugin($plugin_slug)) {
                $this->debug_log("VERIFIED: Adding WordPress.org plugin: {$plugin_name}");
                
                // Add plugin directory files
                $plugin_path = dirname($plugin_dir . '/' . $plugin_file);
                if (is_dir($plugin_path)) {
                    $this->index_directory($plugin_path, self::KNOWN_FILE_PLUGIN, $plugin_data['Version']);
                }
                
                // Add main plugin file
                $main_file = $plugin_dir . '/' . $plugin_file;
                if (is_file($main_file)) {
                    $this->add_known_file($main_file, self::KNOWN_FILE_PLUGIN, $plugin_data['Version']);
                }
                
                $verified_plugins++;
            } else {
                $this->debug_log("CUSTOM: Skipping non-WordPress.org plugin: {$plugin_name} (requires manual review)");
                $custom_plugins++;
            }
        }
        
        $this->debug_log("SECURE: Indexed {$verified_plugins} verified plugins, skipped {$custom_plugins} custom plugins");
    }
    
    /**
     * Check if a plugin exists in WordPress.org repository
     */
    private function is_wordpress_org_plugin($plugin_slug) {
        // Cache API results to avoid repeated calls
        static $plugin_cache = [];
        
        if (isset($plugin_cache[$plugin_slug])) {
            return $plugin_cache[$plugin_slug];
        }
        
        // Query WordPress.org plugin API
        $api_url = "https://api.wordpress.org/plugins/info/1.0/{$plugin_slug}.json";
        $response = wp_remote_get($api_url, ['timeout' => 10]);
        
        if (is_wp_error($response)) {
            $plugin_cache[$plugin_slug] = false;
            return false;
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $is_valid = ($response_code === 200);
        
        $plugin_cache[$plugin_slug] = $is_valid;
        return $is_valid;
    }
    
    /**
     * Index theme files SECURELY
     * Only trust themes from official WordPress.org repository
     */
    private function index_theme_files() {
        if (!function_exists('wp_get_themes')) {
            require_once(ABSPATH . 'wp-includes/theme.php');
        }
        
        $themes = wp_get_themes();
        $verified_themes = 0;
        $custom_themes = 0;
        
        $this->debug_log('Indexing themes using WordPress.org verification (SECURE MODE)...');
        
        // CRITICAL FIX: Extend execution time for theme file indexing
        @set_time_limit(300); // 5 minutes for theme indexing
        
        foreach ($themes as $theme_slug => $theme) {
            $theme_name = $theme->get('Name');
            
            // Verify if theme exists in WordPress.org repository
            if ($this->is_wordpress_org_theme($theme_slug)) {
                $this->debug_log("VERIFIED: Adding WordPress.org theme: {$theme_name}");
                
                $theme_path = $theme->get_stylesheet_directory();
                if (is_dir($theme_path)) {
                    $this->index_directory($theme_path, self::KNOWN_FILE_THEME, $theme->get('Version'));
                }
                
                $verified_themes++;
            } else {
                $this->debug_log("CUSTOM: Skipping non-WordPress.org theme: {$theme_name} (requires manual review)");
                $custom_themes++;
            }
        }
        
        $this->debug_log("SECURE: Indexed {$verified_themes} verified themes, skipped {$custom_themes} custom themes");
    }
    
    /**
     * Check if a theme exists in WordPress.org repository
     */
    private function is_wordpress_org_theme($theme_slug) {
        // Cache API results to avoid repeated calls
        static $theme_cache = [];
        
        if (isset($theme_cache[$theme_slug])) {
            return $theme_cache[$theme_slug];
        }
        
        // Query WordPress.org theme API
        $api_url = "https://api.wordpress.org/themes/info/1.1/?action=theme_information&slug={$theme_slug}";
        $response = wp_remote_get($api_url, ['timeout' => 10]);
        
        if (is_wp_error($response)) {
            $theme_cache[$theme_slug] = false;
            return false;
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $is_valid = ($response_code === 200);
        
        $theme_cache[$theme_slug] = $is_valid;
        return $is_valid;
    }
    
    /**
     * Index all files in a directory
     */
    private function index_directory($directory, $file_type, $version = null) {
        if (!is_dir($directory)) {
            return;
        }
        
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $this->add_known_file($file->getPathname(), $file_type, $version);
            }
        }
    }
    
    /**
     * Add a file to the known files database
     */
    private function add_known_file($file_path, $file_type, $version = null) {
        if (!is_file($file_path) || !is_readable($file_path)) {
            return false;
        }
        
        // Skip files that shouldn't be scanned
        if (!$this->should_scan_file($file_path)) {
            return true; // Not an error, just skipped
        }
        
        // Calculate SHAC hash
        $shac = $this->calculate_shac($file_path);
        if (!$shac) {
            return false;
        }
        
        // Get WordPress relative path
        $wordpress_path = $this->get_wordpress_path($file_path);
        
        // Get file size
        $file_size = filesize($file_path);
        
        // Queue the record for batch insertion
        $this->file_records_queue[] = [
            'file_path' => $wordpress_path,  // Store relative path
            'shac' => $shac,
            'file_type' => $file_type,
            'file_size' => $file_size,
            'version' => $version
        ];
        
        // Process queue if it's getting full
        if (count($this->file_records_queue) >= self::MAX_QUEUED_RECORDS) {
            $this->process_file_records_queue();
        }
        
        return true;
    }
    
    /**
     * Calculate SHAC hash for a file (SHA-256 with normalized content)
     * 
     * SHAC = SHA-256 but with whitespace normalized (removes \n, \r, \t, spaces)
     *
     * @param string $file_path File path
     * @return string|false Binary SHAC hash (32 bytes) or false on failure
     */
    /**
     * Enhanced SHAC calculation with performance optimization and pattern detection (Item #5)
     */
    private function calculate_shac($file_path) {
        if (!file_exists($file_path) || !is_readable($file_path)) {
            $this->debug_log('Cannot read file: ' . $file_path);
            return false;
        }
        
        $file_size = filesize($file_path);
        $start_time = microtime(true);
        
        // Item #5: Performance optimization for large files
        if ($file_size > 5 * 1024 * 1024) { // Files > 5MB
            $shac_result = $this->calculate_shac_large_file($file_path, $file_size);
        } else {
            $shac_result = $this->calculate_shac_standard($file_path, $file_size);
        }
        
        $calculation_time = microtime(true) - $start_time;
        
        // Enhanced debug logging for Item #5
        $this->debug_log(sprintf(
            '[SHAC] File: %s | Size: %s | Time: %.3fs | Hash: %s | Patterns: %s',
            basename($file_path),
            $this->format_file_size($file_size),
            $calculation_time,
            $shac_result['hash'] ? substr(bin2hex($shac_result['hash']), 0, 16) . '...' : 'FAILED',
            isset($shac_result['suspicious_patterns']) ? count($shac_result['suspicious_patterns']) . ' found' : 'none'
        ));
        
        return $shac_result['hash'];
    }
    
    /**
     * Standard SHAC calculation for normal-sized files
     */
    private function calculate_shac_standard($file_path, $file_size) {
        $content = file_get_contents($file_path);
        if ($content === false) {
            $this->debug_log('[SHAC] Failed to read file content: ' . $file_path);
            return ['hash' => false];
        }
        
        // Normalize content by removing newlines, carriage returns, tabs, and spaces
        $normalized = preg_replace('/[\r\n\t\s]+/', '', $content);
        
        // Item #5: Suspicious pattern detection during hashing
        $suspicious_patterns = $this->detect_suspicious_patterns($content, $file_path);
        
        // Calculate SHA-256 hash of normalized content (binary format)
        $hash = hash('sha256', $normalized, true);
        
        return [
            'hash' => $hash,
            'original_size' => strlen($content),
            'normalized_size' => strlen($normalized),
            'suspicious_patterns' => $suspicious_patterns
        ];
    }
    
    /**
     * Memory-efficient SHAC calculation for large files (Item #5)
     */
    private function calculate_shac_large_file($file_path, $file_size) {
        $this->debug_log('[SHAC] Using streaming mode for large file: ' . $this->format_file_size($file_size));
        
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Streaming hash calculation for large files
        $handle = fopen($file_path, 'rb');
        if (!$handle) {
            return ['hash' => false];
        }
        
        $hash_context = hash_init('sha256');
        $chunk_size = 8192; // 8KB chunks
        $total_read = 0;
        $normalized_content = '';
        $suspicious_patterns = [];
        
        // Read file in chunks for memory efficiency
        while (!feof($handle) && $total_read < $file_size) {
            $chunk = fread($handle, $chunk_size);
            if ($chunk === false) break;
            
            $total_read += strlen($chunk);
            
            // Normalize chunk content
            $normalized_chunk = preg_replace('/[\r\n\t\s]+/', '', $chunk);
            $normalized_content .= $normalized_chunk;
            
            // For large files, only check first 64KB for suspicious patterns
            if ($total_read <= 65536) {
                $chunk_patterns = $this->detect_suspicious_patterns($chunk, $file_path);
                $suspicious_patterns = array_merge($suspicious_patterns, $chunk_patterns);
            }
            
            hash_update($hash_context, $normalized_chunk);
            
            // Prevent memory buildup - reset normalized content for very large files
            if (strlen($normalized_content) > 1024 * 1024) { // > 1MB normalized
                $normalized_content = ''; // Reset to save memory
            }
        }
        
        fclose($handle);
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        $hash = hash_final($hash_context, true);
        
        return [
            'hash' => $hash,
            'original_size' => $file_size,
            'normalized_size' => $total_read, // Approximation for large files
            'suspicious_patterns' => array_unique($suspicious_patterns)
        ];
    }
    
    /**
     * Detect suspicious patterns during SHAC calculation (Item #5)
     */
    private function detect_suspicious_patterns($content, $file_path) {
        $patterns = [];
        $file_ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        
        // Only check PHP files for code patterns
        if ($file_ext !== 'php') {
            return $patterns;
        }
        
        // Suspicious PHP patterns (basic detection)
        $suspicious_checks = [
            'eval(' => 'eval_function',
            'base64_decode(' => 'base64_decode',
            'shell_exec(' => 'shell_exec',
            'system(' => 'system_call',
            'exec(' => 'exec_call',
            'file_get_contents(' => 'remote_file_access',
            '$_GET[' => 'get_param_usage',
            '$_POST[' => 'post_param_usage',
            'curl_exec(' => 'curl_usage'
        ];
        
        foreach ($suspicious_checks as $pattern => $pattern_name) {
            if (stripos($content, $pattern) !== false) {
                $patterns[] = $pattern_name;
            }
        }
        
        // Advanced pattern detection for known malware signatures
        if (preg_match('/\b(eval|exec|system|shell_exec)\s*\(\s*\$_(GET|POST|REQUEST)\[/i', $content)) {
            $patterns[] = 'direct_execution_vulnerability';
        }
        
        if (preg_match('/base64_decode\s*\(\s*["\'][A-Za-z0-9+\/=]{100,}/i', $content)) {
            $patterns[] = 'large_base64_payload';
        }
        
        return $patterns;
    }
    
    /**
     * Format file size for human reading
     */
    private function format_file_size($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
    
    /**
     * Get WordPress relative path from absolute path
     */
    private function get_wordpress_path($file_path) {
        $abspath = rtrim(ABSPATH, '/\\');
        $normalized_file = str_replace('\\', '/', $file_path);
        $normalized_abspath = str_replace('\\', '/', $abspath);
        
        if (strpos($normalized_file, $normalized_abspath) === 0) {
            return ltrim(substr($normalized_file, strlen($normalized_abspath)), '/');
        }
        
        return $file_path;
    }
    
    /**
     * Process the file records queue
     */
    private function process_file_records_queue() {
        if (empty($this->file_records_queue)) {
            return;
        }
        
        $values = [];
        $placeholders = [];
        
        foreach ($this->file_records_queue as $record) {
            $placeholders[] = "(%s, %s, %s, %d, %s, %s)";
            $values[] = $record['file_path'];
            $values[] = $record['file_type'];
            $values[] = $record['shac'];
            $values[] = $record['file_size'];
            $values[] = current_time('mysql');
            $values[] = $record['version'];
        }
        
        if (!empty($placeholders)) {
            // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic bulk insert with prepared values
            $sql = "INSERT IGNORE INTO {$this->table_name} 
                    (file_path, file_type, shac, file_size, last_updated, version) 
                    VALUES " . implode(',', $placeholders);
            
            $result = $this->wpdb->query($this->wpdb->prepare($sql, $values));
            // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $this->debug_log("Processed queue: {$result} files added to database");
        }
        
        // Clear the queue
        $this->file_records_queue = [];
    }
    
    /**
     * Check if a file is known (legitimate)
     */
    public function is_known_file($file_path) {
        $shac = $this->calculate_shac($file_path);
        if (!$shac) {
            return false;
        }
        
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared query with class property table name
        $result = $this->wpdb->get_var($this->wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE shac = %s",
            $shac
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $result > 0;
    }
    
    /**
     * Check if a file is a known core file
     */
    public function is_known_core_file($file_path) {
        return $this->is_known_file_type($file_path, self::KNOWN_FILE_CORE);
    }
    
    /**
     * Check if a file is a known plugin file
     */
    public function is_known_plugin_file($file_path) {
        return $this->is_known_file_type($file_path, self::KNOWN_FILE_PLUGIN);
    }
    
    /**
     * Check if a file is a known theme file
     */
    public function is_known_theme_file($file_path) {
        return $this->is_known_file_type($file_path, self::KNOWN_FILE_THEME);
    }
    
    /**
     * Check if a file is known and of a specific type
     */
    private function is_known_file_type($file_path, $file_type) {
        $shac = $this->calculate_shac($file_path);
        if (!$shac) {
            return false;
        }
        
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared query with class property table name
        $result = $this->wpdb->get_var($this->wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE shac = %s AND file_type = %s",
            $shac,
            $file_type
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $result > 0;
    }
    
    /**
     * Get file information from known files database
     */
    public function get_file_info($file_path) {
        $shac = $this->calculate_shac($file_path);
        if (!$shac) {
            return null;
        }
        
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared query with class property table name
        return $this->wpdb->get_row($this->wpdb->prepare(
            "SELECT * FROM {$this->table_name} WHERE shac = %s LIMIT 1",
            $shac
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    }
    
    /**
     * Get statistics about known files
     */
    public function get_known_files_stats() {
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property
        $stats = [];
        $stats['total'] = $this->wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
        
        // Get counts by file type
        $results = $this->wpdb->get_results(
            "SELECT file_type, COUNT(*) as count FROM {$this->table_name} GROUP BY file_type",
            ARRAY_A
        );
        
        foreach ($results as $result) {
            $stats[$result['file_type']] = $result['count'];
        }
        
        // Last updated
        $stats['last_updated'] = $this->wpdb->get_var("SELECT MAX(last_updated) FROM {$this->table_name}");
        
        // Unique components
        $stats['unique_components'] = $this->wpdb->get_var("SELECT COUNT(DISTINCT component) FROM {$this->table_name} WHERE component IS NOT NULL");
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $stats;
    }
    
    /**
     * Update known files database (rebuild)
     */
    public function update_known_files() {
        $this->initialized = false;
        return $this->initialize_known_files();
    }
    
    /**
     * Check if known files database needs updating
     * Compares database count with expected WordPress core file count
     */
    public function needs_update() {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property
        $db_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
        
        // If database is empty or very small, needs update
        if ($db_count < 100) {
            return true;
        }
        
        // Get expected core file count from WordPress.org checksums
        global $wp_version;
        $checksums = $this->get_wordpress_checksums();
        
        if (empty($checksums)) {
            // Can't verify, assume database is okay if it has files
            return false;
        }
        
        // Get count of core files in database
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property
        $core_count = $this->wpdb->get_var($this->wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE file_type = %s",
            self::KNOWN_FILE_CORE
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        $expected_core_count = count($checksums);
        
        // If core file counts don't match, needs update
        if ($core_count !== $expected_core_count) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Get list of unknown files in specified directories
     */
    public function get_unknown_files($directories = null) {
        if (!$this->initialized) {
            $this->initialize_known_files();
        }
        
        if ($directories === null) {
            $directories = [
                ABSPATH . 'wp-admin',
                ABSPATH . 'wp-includes',
                WP_CONTENT_DIR . '/plugins',
                WP_CONTENT_DIR . '/themes'
            ];
        }
        
        $unknown_files = [];
        
        foreach ($directories as $directory) {
            if (!is_dir($directory)) {
                continue;
            }
            
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::LEAVES_ONLY
            );
            
            foreach ($iterator as $file) {
                if ($file->isFile()) {
                    $file_path = $file->getPathname();
                    if (!$this->is_known_file($file_path)) {
                        $unknown_files[] = $file_path;
                    }
                }
            }
        }
        
        return $unknown_files;
    }
    
    /**
     * Add debug log entry
     *
     * @param string $message Log message
     */
    private function debug_log($message) {
        $this->debug_log[] = '[' . current_time('H:i:s') . '] ' . $message;
        
        // Also log to error log if WP_DEBUG is enabled
        // if (defined('WP_DEBUG') && WP_DEBUG) {
        //     error_log('VMPFence Known Files: ' . $message);
        // }
    }
    
    /**
     * Get debug logs
     *
     * @return array Debug log entries
     */
    public function get_debug_log() {
        return $this->debug_log;
    }
    
    /**
     * Clear debug logs
     */
    public function clear_debug_log() {
        $this->debug_log = [];
    }
    
    /**
     * Check if file should be scanned (exclude binary files, etc.)
     *
     * @param string $file_path File path
     * @return bool True if should be scanned
     */
    private function should_scan_file($file_path) {
        // Get file extension
        $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        
        // Scannable extensions (text-based files)
        $scannable_extensions = [
            'php', 'js', 'css', 'html', 'htm', 'txt', 'json', 'xml', 'svg',
            'md', 'readme', 'sql', 'htaccess', 'gitignore', 'yml', 'yaml'
        ];
        
        // Always scan files without extensions (like .htaccess)
        if (empty($extension)) {
            return true;
        }
        
        return in_array($extension, $scannable_extensions);
    }
    
    /**
     * Enhanced known file checking with both path and SHAC verification
     *
     * @param string $file_path Full file path
     * @param string $relative_path Relative path from WordPress root
     * @return bool True if file is known
     */
    public function is_known_file_enhanced($file_path, $relative_path) {
        // First check by path
        if ($this->is_known_file_by_path($relative_path)) {
            $this->debug_log("File known by path: {$relative_path}");
            return true;
        }
        
        // Then check by SHAC hash
        $shac = $this->calculate_shac($file_path);
        if ($shac && $this->is_known_file_by_shac($shac)) {
            $this->debug_log("File known by SHAC: {$relative_path}");
            return true;
        }
        
        return false;
    }
    
    /**
     * Check if file is known by SHAC hash
     *
     * @param string $shac_hash Binary SHAC hash
     * @param string $file_type Optional file type filter
     * @return bool True if file is known
     */
    public function is_known_file_by_shac($shac_hash, $file_type = null) {
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query with prepared values and class property table name
        $sql = "SELECT COUNT(*) FROM {$this->table_name} WHERE shac = %s";
        $params = [$shac_hash];
        
        if ($file_type) {
            $sql .= " AND file_type = %s";
            $params[] = $file_type;
        }
        
        $count = $this->wpdb->get_var($this->wpdb->prepare($sql, $params));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $count > 0;
    }
    
    /**
     * Check if file is known by path
     *
     * @param string $file_path Relative file path
     * @param string $file_type Optional file type filter
     * @return bool True if file is known
     */
    public function is_known_file_by_path($file_path, $file_type = null) {
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic query with prepared values and class property table name
        $sql = "SELECT COUNT(*) FROM {$this->table_name} WHERE file_path = %s";
        $params = [$file_path];
        
        if ($file_type) {
            $sql .= " AND file_type = %s";
            $params[] = $file_type;
        }
        
        $count = $this->wpdb->get_var($this->wpdb->prepare($sql, $params));
        // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $count > 0;
    }
}
