<?php
/**
 * WAF Parameter Allowlist Model
 * 
 * Manages allowlisted URL parameters that bypass specific WAF rules.
 * Uses base64-encoded composite keys for storage.
 *
 * @package VMPFence
 * @subpackage Firewall\Models
 */

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

class VMPFence_Param_Allowlist {
    
    /**
     * Config key for storing allowlisted parameters
     */
    const CONFIG_KEY = 'allowlisted_url_params';
    
    /**
     * Add allowlist entry for a parameter and rule combination
     *
     * @param string $path URL path (e.g., '/wp-admin/admin-ajax.php')
     * @param string $param_key Parameter name (e.g., 'user_query')
     * @param string $rule_id Rule ID to bypass, or 'all' to bypass all rules
     * @param array $metadata Additional data (description, source, ip)
     * @return bool Success status
     */
    public static function allowlist_rule_for_param($path, $param_key, $rule_id, $metadata = array()) {
        // Get current allowlist config
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());
        
        // Generate base64 composite key
        $composite_key = self::generate_composite_key($path, $param_key);
        
        // Initialize entry if doesn't exist
        if (!isset($allowlist[$composite_key])) {
            $allowlist[$composite_key] = array();
        }
        
        // Prepare metadata with defaults
        $entry_data = array_merge(array(
            'timestamp' => time(),
            'source' => 'manual',
            'user' => self::get_current_user(),
            'ip' => self::get_client_ip(),
            'disabled' => false
        ), $metadata);
        // Add rule entry
        $allowlist[$composite_key][$rule_id] = $entry_data;
        
        // Save to config
        //return VMPFence_Config::set(self::CONFIG_KEY, $allowlist);
        $result = VMPFence_Config::set(self::CONFIG_KEY, $allowlist);
        
        // Read back from database to verify
        $saved_data = VMPFence_Config::get(self::CONFIG_KEY, array());
        
        return $result;
    }
    
    /**
     * Check if a parameter is allowlisted for a specific rule
     *
     * @param string $rule_id Rule ID to check
     * @param string $path URL path
     * @param string $param_key Parameter name
     * @return bool True if allowlisted and not disabled
     */
    public static function is_rule_param_allowlisted($rule_id, $path, $param_key) {
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());

        // Generate composite key
        $composite_key = self::generate_composite_key($path, $param_key);
        
        // Check if entry exists
        if (!isset($allowlist[$composite_key])) {
            return false;
        }
        
        $entry = $allowlist[$composite_key];
        
        // Check for 'all' keyword (bypass all rules)
        if (isset($entry['all']) && !$entry['all']['disabled']) {
            return true;
        }
        
        // Check for specific rule ID
        if (isset($entry[$rule_id]) && !$entry[$rule_id]['disabled']) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Remove allowlist entry for specific parameter and rule
     *
     * @param string $path URL path
     * @param string $param_key Parameter name
     * @param string $rule_id Rule ID to remove, or 'all'
     * @return bool Success status
     */
    public static function remove_allowlist($path, $param_key, $rule_id) {
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());
        
        $composite_key = self::generate_composite_key($path, $param_key);
        
        // Check if entry exists
        if (!isset($allowlist[$composite_key])) {
            return false;
        }
        
        // Remove specific rule entry
        if (isset($allowlist[$composite_key][$rule_id])) {
            unset($allowlist[$composite_key][$rule_id]);
            
            // Remove entire key if no rules left
            if (empty($allowlist[$composite_key])) {
                unset($allowlist[$composite_key]);
            }
            
            return VMPFence_Config::set(self::CONFIG_KEY, $allowlist);
        }
        
        return false;
    }
    
    /**
     * Toggle disabled status for an allowlist entry
     *
     * @param string $path URL path
     * @param string $param_key Parameter name
     * @param string $rule_id Rule ID
     * @return bool Success status
     */
    public static function toggle_allowlist($path, $param_key, $rule_id) {
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());
        
        $composite_key = self::generate_composite_key($path, $param_key);
        
        if (!isset($allowlist[$composite_key][$rule_id])) {
            return false;
        }
        
        // Toggle disabled flag
        $current_status = isset($allowlist[$composite_key][$rule_id]['disabled']) 
            ? $allowlist[$composite_key][$rule_id]['disabled'] 
            : false;
        
        $allowlist[$composite_key][$rule_id]['disabled'] = !$current_status;
        
        return VMPFence_Config::set(self::CONFIG_KEY, $allowlist);
    }
    
    /**
     * Get all allowlisted parameters with decoded values for admin display
     *
     * @return array Decoded allowlist entries
     */
    public static function get_all_allowlisted_params() {
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());
        $decoded = array();
        
        // Ensure allowlist is an array
        if (!is_array($allowlist)) {
            return $decoded;
        }
        
        foreach ($allowlist as $composite_key => $rules) {
            // Decode composite key
            $parts = self::decode_composite_key($composite_key);
            
            if ($parts && is_array($rules)) {
                foreach ($rules as $rule_id => $metadata) {
                    $decoded[] = array(
                        'path' => $parts['path'],
                        'param_key' => $parts['param_key'],
                        'rule_id' => $rule_id,
                        'composite_key' => $composite_key,
                        'metadata' => $metadata
                    );
                }
            }
        }
        
        return $decoded;
    }
    
    /**
     * Get allowlist cache for WAF (indexed by composite_key)
     * 
     * @since 2.0.0
     * @return array Allowlist indexed by composite_key
     */
    public static function get_allowlist_cache() {
        $allowlist = VMPFence_Config::get(self::CONFIG_KEY, array());
        
        // Ensure allowlist is an array
        if (!is_array($allowlist)) {
            return array();
        }
        
        // Return the allowlist directly in the format:
        // [composite_key => [rule_id => metadata, ...], ...]
        return $allowlist;
    }
    
    /**
     * Clear all allowlist entries
     *
     * @return bool Success status
     */
    public static function clear_allowlist() {
        return VMPFence_Config::set(self::CONFIG_KEY, array());
    }
    
    /**
     * Generate base64-encoded composite key from path and parameter
     *
     * @param string $path URL path
     * @param string $param_key Parameter name
     * @return string Base64 composite key (format: base64(path)|base64(param))
     */
    public static function generate_composite_key($path, $param_key) {
        $encoded_path = base64_encode($path);
        $encoded_param = base64_encode($param_key);
        return $encoded_path . '|' . $encoded_param;
    }
    
    /**
     * Decode composite key back to path and parameter
     *
     * @param string $composite_key Base64 composite key
     * @return array|false Array with 'path' and 'param_key', or false on failure
     */
    private static function decode_composite_key($composite_key) {
        $parts = explode('|', $composite_key);
        
        if (count($parts) !== 2) {
            return false;
        }
        
        $path = base64_decode($parts[0], true);
        $param_key = base64_decode($parts[1], true);
        
        if ($path === false || $param_key === false) {
            return false;
        }
        
        return array(
            'path' => $path,
            'param_key' => $param_key
        );
    }
    
    /**
     * Get current WordPress user login
     *
     * @return string Username or '-' if not logged in
     */
    private static function get_current_user() {
        $user = wp_get_current_user();
        return $user->exists() ? $user->user_login : '-';
    }
    
    /**
     * Get client IP address for audit metadata
     *
     * @return string Client IP address
     */
    private static function get_client_ip() {
        if (class_exists('VMPFence_Request')) {
            $request = new VMPFence_Request();
            return $request->get_ip();
        }
        
        // Fallback
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }
}
