<?php
/**
 * WAF Rule Model
 *
 * @package VMPFence
 * @since 2.0.0
 * @since 2.1.0 - Added hybrid local + API rule fetching and syncing
 * @since 2.2.4 - Added refresh of license data after successful API sync to ensure license key is up-to-date
 * @since 2.2.5 Added enhanced Audit Log
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * WAF Rule Model Class
 *
 * @since 2.0.0
 */
class VMPFence_WAF_Rule {

    /**
     * Get enabled rules (hybrid: local + API)
     *
     * @since 2.0.0
     * @since 2.1.0 - Added API integration for premium users
     * @return array
     */
    public static function get_enabled_rules() {
        // Load local rules (always primary)
        $local_rules = self::get_local_enabled_rules();
        
        // If API available and premium, add API rules
        if (self::should_use_api()) {
            try {
                $api_rules = self::get_api_rules();
                
                if (!empty($api_rules)) {
                    // Get disabled categories from local database
                    global $wpdb;
                    $table_name = $wpdb->prefix . 'vmpfence_waf_rules';
                    $disabled_categories = $wpdb->get_col(
                        "SELECT DISTINCT LOWER(category) FROM $table_name WHERE enabled = 0"
                    );
                    
                    // Merge and deduplicate by rule_id (local takes precedence)
                    $rules_by_id = array();
                    
                    // First, add API rules (but skip categories that are disabled locally)
                    foreach ($api_rules as $rule) {
                        if (isset($rule['rule_id']) && isset($rule['category'])) {
                            $rule_category = strtolower(trim($rule['category']));
                            // Skip API rules if their category is disabled locally
                            if (!in_array($rule_category, $disabled_categories)) {
                                $rules_by_id[$rule['rule_id']] = $rule;
                            }
                        }
                    }
                    
                    // Then, add/override with local enabled rules (local wins)
                    foreach ($local_rules as $rule) {
                        if (isset($rule['rule_id'])) {
                            $rules_by_id[$rule['rule_id']] = $rule;
                        }
                    }
                    
                    // Convert back to indexed array
                    $rules = array_values($rules_by_id);
                    
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::debug('WAF: Loaded ' . count($local_rules) . ' local + ' . count($api_rules) . ' API rules = ' . count($rules) . ' total (deduplicated, ' . count($disabled_categories) . ' categories filtered)');
                    }
                    
                    return $rules;
                }
            } catch (Exception $e) {
                // API failed - graceful fallback to local only
                if (class_exists('VMPFence_Secure_Logger')) {
                    VMPFence_Secure_Logger::warning('WAF API rules fetch failed, using local only', ['error' => $e->getMessage()]);
                }
            }
        }
        
        // Return local rules only (API unavailable or failed)
        return $local_rules;
    }
    
    /**
     * Get local enabled rules from database
     *
     * @since 2.1.0
     * @return array
     */
    private static function get_local_enabled_rules() {
        // Try to get from cache
        $cache_key = 'vmpfence_waf_enabled_rules';
        $rules = wp_cache_get($cache_key);

        if (false === $rules) {
            global $wpdb;
            $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

            $rules = $wpdb->get_results(
                "SELECT * FROM $table_name WHERE enabled = 1 ORDER BY category, severity DESC",
                ARRAY_A
            );

            // Cache for 1 hour
            wp_cache_set($cache_key, $rules, '', 3600);
        }

        return $rules;
    }
    
    /**
     * Check if API should be used for rules
     *
     * @since 2.1.0
     * @since 2.2.2 - Fixed to allow free tier API access (free users get limited rules from API)
     * @return bool
     */
    private static function should_use_api() {
        // Check if API client is available
        if (!class_exists('VMPFence_API_Client')) {
            return false;
        }
        
        // Check if we have an API key
        if (!class_exists('VMPFence_Config')) {
            return false;
        }
        
        $api_key = VMPFence_Config::get('central_api_key', '');
        if (empty($api_key)) {
            return false;
        }
        
        // ✅ FIXED: Both free and premium users can use API
        // Free users get basic rules, premium users get full rule set
        // The API server handles tier-specific filtering
        return true;
    }
    
    /**
     * Get API rules (cached)
     *
     * @since 2.1.0
     * @return array
     */
    private static function get_api_rules() {
        // Check transient cache (1 hour)
        $cache_key = 'vmpfence_api_waf_rules';
        $cached = get_transient($cache_key);
        
        if ($cached !== false && is_array($cached)) {
            return $cached;
        }
        
        // Fetch from API
        $api_key = VMPFence_Config::get('central_api_key', '');
        if (empty($api_key)) {
            return array();
        }
        
        try {
            $api_client = new VMPFence_API_Client();
            $response = $api_client->get_firewall_rules($api_key);
            
            if (is_wp_error($response)) {
                if (class_exists('VMPFence_Secure_Logger')) {
                    VMPFence_Secure_Logger::warning('WAF API rules fetch error', ['error' => $response->get_error_message()]);
                }
                return array();
            }
            
            if (isset($response['rules']) && is_array($response['rules'])) {
                $rules = $response['rules'];
                
                // Cache for 1 hour
                set_transient($cache_key, $rules, HOUR_IN_SECONDS);
                
                if (class_exists('VMPFence_Secure_Logger')) {
                    VMPFence_Secure_Logger::info('WAF API rules fetched successfully', ['count' => count($rules)]);
                }
                
                return $rules;
            }
        } catch (Exception $e) {
            if (class_exists('VMPFence_Secure_Logger')) {
                VMPFence_Secure_Logger::error('WAF API rules fetch exception', ['error' => $e->getMessage()]);
            }
        }
        
        return array();
    }
    
    /**
     * Sync API rules to local database (runs via cron)
     *
     * @since 2.1.0
     * @since 2.2.2 - Added defensive checks for required classes
     * @return array Sync statistics
     */
    public static function sync_from_api() {
        $stats = array(
            'success' => false,
            'total' => 0,
            'new' => 0,
            'updated' => 0,
            'errors' => array()
        );
        
        // DEFENSIVE: Ensure required classes are loaded
        if (!class_exists('VMPFence_API_Client')) {
            $stats['errors'][] = 'VMPFence_API_Client class not loaded';
            return $stats;
        }
        
        if (!class_exists('VMPFence_Config')) {
            $stats['errors'][] = 'VMPFence_Config class not loaded';
            return $stats;
        }
        
        // Check if API should be used
        if (!self::should_use_api()) {
            $stats['errors'][] = 'API not available (no API key found or API client not loaded)';
            return $stats;
        }
        
        // Fetch API rules
        $api_rules = self::get_api_rules();
        
        if (empty($api_rules)) {
            $stats['errors'][] = 'No API rules returned';
            return $stats;
        }
        
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';
        $stats['total'] = count($api_rules);
        
        foreach ($api_rules as $rule) {
            // Validate required fields
            if (!isset($rule['rule_id']) || !isset($rule['pattern'])) {
                continue;
            }
            
            // Check if rule exists (by rule_id only, not source)
            $exists = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM $table_name WHERE rule_id = %s",
                $rule['rule_id']
            ));
            
            // Convert severity to integer if it's a string
            $severity = isset($rule['severity']) ? $rule['severity'] : 'medium';
            if (is_string($severity)) {
                // Map severity strings to numeric values
                $severity_map = array(
                    'critical' => 5,
                    'high' => 4,
                    'medium' => 3,
                    'low' => 2,
                    'info' => 1
                );
                $severity = isset($severity_map[$severity]) ? $severity_map[$severity] : 3;
            }
            
            $data = array(
                'rule_id' => $rule['rule_id'],
                'pattern' => $rule['pattern'],
                'category' => $rule['category'] ?? 'other',
                'severity' => (int)$severity,
                'enabled' => isset($rule['enabled']) ? (int)$rule['enabled'] : 1,
                'description' => $rule['description'] ?? '',
                'version' => $rule['version'] ?? '1.0',
                'action' => $rule['action'] ?? 'block'
            );
            
            if ($exists) {
                // Update existing rule
                $result = $wpdb->update(
                    $table_name,
                    $data,
                    array('rule_id' => $rule['rule_id']),
                    array('%s', '%s', '%s', '%d', '%d', '%s', '%s', '%s'),
                    array('%s')
                );
                if ($result !== false) {
                    $stats['updated']++;
                }
            } else {
                // Insert new rule
                $result = $wpdb->insert($table_name, $data);
                if ($result !== false) {
                    $stats['new']++;
                }
            }
        }
        
        // Clear cache
        wp_cache_delete('vmpfence_waf_enabled_rules');
        delete_transient('vmpfence_api_waf_rules');
        
        $stats['success'] = true;
        
        if (class_exists('VMPFence_Secure_Logger')) {
            VMPFence_Secure_Logger::info('WAF rules synced from API', $stats);
        }
        
        // Refresh license data after successful sync (ensures license key is up-to-date)
        $license_refreshed = false;
        if (class_exists('VMPFence_License') && class_exists('VMPFence_Config') && class_exists('VMPFence_API_Client')) {
            try {
                $api_key = VMPFence_Config::get('central_api_key', '');
                if (!empty($api_key)) {
                    $api_client = new VMPFence_API_Client();
                    $license_data = $api_client->check_api_key($api_key);
                    if (!is_wp_error($license_data) && isset($license_data['license_key'])) {
                        VMPFence_License::update_license_settings($license_data);
                        $license_refreshed = true;
                    }
                }
            } catch (Exception $e) {
                // Silently fail - license refresh is not critical for sync operation
            }
        }
        
        $stats['license_refreshed'] = $license_refreshed;
        return $stats;
    }

    /**
     * Get rules by category
     *
     * @since 2.0.0
     * @param string $category Rule category
     * @return array
     */
    public static function get_by_category($category) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM $table_name WHERE category = %s ORDER BY severity DESC",
                $category
            ),
            ARRAY_A
        );
    }

    /**
     * Get rules by action
     *
     * @since 2.0.0
     * @param string $action Rule action (block, log)
     * @return array
     */
    public static function get_by_action($action) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM $table_name WHERE action = %s ORDER BY category, severity DESC",
                $action
            ),
            ARRAY_A
        );
    }

    /**
     * Toggle rule enabled status
     *
     * @since 2.0.0
     * @param int $rule_id Rule ID
     * @return bool
     */
    public static function toggle_rule($rule_id) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        $result = $wpdb->query(
            $wpdb->prepare(
                "UPDATE $table_name SET enabled = NOT enabled WHERE id = %d",
                $rule_id
            )
        );

        // Clear cache
        wp_cache_delete('vmpfence_waf_enabled_rules');

        return $result !== false;
    }

    /**
     * Add new rule
     *
     * @since 2.0.0
     * @param array $rule_data Rule data
     * @return int|false Rule ID or false on failure
     */
    public static function add_rule($rule_data) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        // Check if rule_id already exists
        if (isset($rule_data['rule_id'])) {
            $exists = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM $table_name WHERE rule_id = %s",
                    $rule_data['rule_id']
                )
            );
            
            if ($exists > 0) {
                // Rule already exists, skip insertion
                return false;
            }
        }

        // Validate pattern
        if (!self::validate_pattern($rule_data['pattern'])) {
            return false;
        }

        $defaults = array(
            'enabled' => 1,
            'severity' => 0,
            'version' => '1.0',
            'score' => 50,
            'action' => 'block',
            'whitelist' => 0,
        );

        $rule_data = wp_parse_args($rule_data, $defaults);

        $result = $wpdb->insert(
            $table_name,
            array(
                'rule_id' => $rule_data['rule_id'],
                'enabled' => $rule_data['enabled'],
                'category' => $rule_data['category'],
                'severity' => $rule_data['severity'],
                'pattern' => $rule_data['pattern'],
                'description' => $rule_data['description'],
                'version' => $rule_data['version'],
                'score' => $rule_data['score'],
                'action' => $rule_data['action'],
                'whitelist' => $rule_data['whitelist'],
            ),
            array('%s', '%d', '%s', '%d', '%s', '%s', '%s', '%d', '%s', '%d')
        );

        if ($result) {
            // Clear cache
            wp_cache_delete('vmpfence_waf_enabled_rules');

            // Fire hook for audit log observer
            do_action('vmpfence_waf_rule_created', array(
                'rule_id' => $wpdb->insert_id,
                'rule_data' => $rule_data
            ));

            return $wpdb->insert_id;
        }

        return false;
    }

    /**
     * Install default rules
     *
     * @since 2.0.0
     * @param bool $force_clear Force clear existing rules before import (used during activation)
     * @return int Number of rules installed
     */
    public static function install_default_rules($force_clear = false) {
        $installed = 0;

        // Try JSON import first
        if (class_exists('VMPFence_Rule_Importer')) {
            $importer = new VMPFence_Rule_Importer();
            $expected_count = $importer->get_expected_count();
            
            // If not forcing clear, check if rules already match expected count
            if (!$force_clear && self::has_default_rules() && $expected_count !== null && $importer->verify_rules_count($expected_count)) {
                // Rules exist and count matches, skip installation
                global $wpdb;
                $table_name = $wpdb->prefix . 'vmpfence_waf_rules';
                $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
                return intval($count);
            }
            
            // Clear all existing rules before import
            if (self::has_default_rules()) {
                self::delete_all_rules();
            }
            
            // Import from JSON
            $result = $importer->import_from_json();

            if ($result['success']) {
                $installed = $result['imported'];
                
                // Verify rule count matches expected after import
                if ($expected_count !== null && !$importer->verify_rules_count($expected_count)) {
                    // Count mismatch after import, clear and retry
                    self::delete_all_rules();
                    $retry_result = $importer->retry_import(3);
                    if ($retry_result['success']) {
                        $installed = $retry_result['imported'];
                    }
                }

                // If we have successfully imported rules, return count
                if ($installed > 0) {
                    return $installed;
                }
            }
        }

        
        // Fallback to hardcoded rules if JSON import failed
        $default_rules = array(
            // XSS Rules
            array(
                'rule_id' => 'xss-001',
                'category' => 'XSS',
                'severity' => 3,
                'pattern' => '/<script[^>]*>.*?<\/script>/i',
                'description' => 'Detects script tags',
                'score' => 50,
                'action' => 'block',
                'whitelist' => 0,
            ),
            array(
                'rule_id' => 'xss-002',
                'category' => 'XSS',
                'severity' => 3,
                'pattern' => '/on(load|unload|error|abort|click|dblclick|mouse(down|up|over|out|enter|leave|move)|focus|blur|change|submit|reset|select|keypress|keydown|keyup|contextmenu|drag|drop|copy|cut|paste|scroll|resize|beforeunload)[\s]*=/i',
                'description' => 'Detects inline event handlers',
                'score' => 50,
                'action' => 'block',
                'whitelist' => 0,
            ),
            // SQLi Rules
            array(
                'rule_id' => 'sqli-001',
                'category' => 'SQLi',
                'severity' => 4,
                'pattern' => '/union[\s]+select/i',
                'description' => 'Detects UNION SELECT statements',
                'score' => 50,
                'action' => 'block',
                'whitelist' => 0,
            ),
            array(
                'rule_id' => 'sqli-002',
                'category' => 'SQLi',
                'severity' => 3,
                'pattern' => '/(\' or \'1\'=\'1|\" or \"1\"=\"1|or 1=1)/i',
                'description' => 'Detects SQL tautology',
                'score' => 50,
                'action' => 'block',
                'whitelist' => 0,
            ),
            // RFI Rule
            array(
                'rule_id' => 'rfi-001',
                'category' => 'RFI',
                'severity' => 4,
                'pattern' => '/(http|https|ftp):\/\/[^\s]+(\.php|\.txt|\.js)/i',
                'description' => 'Detects remote file inclusion',
                'score' => 50,
                'action' => 'block',
                'whitelist' => 0,
            ),
            // LFI Rule
            array(
                'rule_id' => 'lfi-001',
                'category' => 'LFI',
                'severity' => 3,
                'pattern' => '/(\.\.(\/|\\\\)){2,}/i',
                'description' => 'Detects directory traversal',
                'score' => 40,
                'action' => 'block',
                'whitelist' => 0,
            ),
            // RCE Rule
            array(
                'rule_id' => 'rce-001',
                'category' => 'RCE',
                'severity' => 5,
                'pattern' => '/(exec|shell_exec|system|passthru|eval|base64_decode|assert|popen)[\s]*\(/i',
                'description' => 'Detects command execution functions',
                'score' => 60,
                'action' => 'block',
                'whitelist' => 0,
            ),
        );

        foreach ($default_rules as $rule) {
            if (self::add_rule($rule)) {
                $installed++;
            }
        }

        return $installed;
    }

    /**
     * Validate regex pattern
     *
     * @since 2.0.0
     * @param string $pattern Regex pattern
     * @return bool
     */
    public static function validate_pattern($pattern) {
        // Test if pattern is valid regex
        set_error_handler(function() {}, E_WARNING);
        $valid = @preg_match($pattern, '') !== false;
        restore_error_handler();

        return $valid;
    }

    /**
     * Get all rules
     *
     * @since 2.0.0
     * @return array
     */
    public static function get_all() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        return $wpdb->get_results(
            "SELECT * FROM $table_name ORDER BY category, severity DESC",
            ARRAY_A
        );
    }

    /**
     * Get rule by ID
     *
     * @since 2.0.0
     * @param int $id Rule ID
     * @return array|null
     */
    public static function get_by_id($id) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        return $wpdb->get_row(
            $wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id),
            ARRAY_A
        );
    }

    /**
     * Check if default rules are installed
     *
     * @since 2.0.0
     * @return bool
     */
    public static function has_default_rules() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");

        return intval($count) > 0;
    }

    /**
     * Delete all rules (used for fresh import)
     *
     * @since 2.0.0
     * @return bool
     */
    public static function delete_all_rules() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_waf_rules';

        // Clear cache first
        wp_cache_delete('vmpfence_waf_enabled_rules');

        // Fire hook for audit log observer
        do_action('vmpfence_waf_rule_deleted', array('scope' => 'all'));

        // Delete all rules
        $result = $wpdb->query("TRUNCATE TABLE $table_name");

        return $result !== false;
    }
}
