<?php
/**
 * Endpoint Mapper Class
 *
 * Manages secure endpoint mapping to prevent API structure exposure
 * 
 * **Security Enhancement**: Uses obfuscated endpoint codes instead of readable paths
 * **Phase**: 13.4 - Secure Endpoint Mapping
 * 
 * @package VMPFence
 * @since 1.2.0
 * @since 2.2.4 - Added handling for SIEM Phase 3.5 dynamic endpoints with UUID parameters
 */

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

class VMPFence_Endpoint_Mapper {
    
    /**
     * Cached endpoint map (code => path)
     * @var array
     */
    private static $endpoint_map = null;
    
    /**
     * Fallback mode flag
     * If true, endpoints cannot be resolved (server unreachable)
     * @var bool
     */
    private static $fallback_mode = false;
    
    /**
     * Get endpoint path from logical name
     * 
     * Uses obfuscated mapping from server. If server unreachable, triggers error.
     * 
     * @param string $logical_name Logical endpoint identifier (may include query params)
     * @return string Obfuscated endpoint code (with query params preserved) or logical name if unavailable
     */
    public static function get_endpoint($logical_name) {
        // Load endpoint map if not already loaded
        if (self::$endpoint_map === null) {
            self::load_endpoint_map();
        }
        
        // Strip query parameters for mapping lookup
        $query_string = '';
        if (strpos($logical_name, '?') !== false) {
            list($endpoint_path, $query_string) = explode('?', $logical_name, 2);
        } else {
            $endpoint_path = $logical_name;
        }
        
        // First try exact match
        if (!empty(self::$endpoint_map[$endpoint_path])) {
            $mapped_code = self::$endpoint_map[$endpoint_path];
            // Append query string if present
            return $query_string ? $mapped_code . '?' . $query_string : $mapped_code;
        }
        
        // Try pattern matching for dynamic endpoints (e.g., yara/batch/{batch_id}/status)
        $matched_code = self::match_dynamic_endpoint($endpoint_path);
        if ($matched_code !== null) {
            // Append query string if present
            return $query_string ? $matched_code . '?' . $query_string : $matched_code;
        }
        
        // No mapping available - server unreachable or endpoint not found
        // Log error with the actual endpoint path (without query params for clarity)
        if (class_exists('VMPFence_Secure_Logger')) {
            VMPFence_Secure_Logger::error('Endpoint mapping unavailable', array(
                'endpoint_path' => $endpoint_path,
                'has_query' => !empty($query_string),
                'fallback_mode' => self::$fallback_mode,
                'available_endpoints' => count(self::$endpoint_map)
            ));
        }
        
        // Return logical name as last resort (API will likely return 404)
        // This is intentional - we prefer API errors over exposing structure
        return $logical_name;
    }
    
    /**
     * Load endpoint map from server configuration
     * 
     * The endpoint map is fetched from /system/config/endpoints
     * and cached locally
     */
    private static function load_endpoint_map() {
        // Try to get from endpoint config
        $config = VMPFence_Endpoint_Config::get_config();
        
        if (!empty($config['endpoint_map']) && is_array($config['endpoint_map'])) {
            // Server returns: code => path
            // We need: path => code (to lookup code by path)
            self::$endpoint_map = array();
            
            foreach ($config['endpoint_map'] as $code => $path) {
                // Map path to code correctly
                self::$endpoint_map[$path] = $code;
            }
            
            self::$fallback_mode = false;
        } else {
            // No endpoint map available - server unreachable
            self::$endpoint_map = array();
            self::$fallback_mode = true;
            
            if (class_exists('VMPFence_Secure_Logger')) {
                VMPFence_Secure_Logger::warning('Endpoint map unavailable - server may be unreachable');
            }
        }
    }
    
    /**
     * Refresh endpoint map from server
     * 
     * Forces a refresh of the endpoint configuration
     */
    public static function refresh_map() {
        self::$endpoint_map = null;
        VMPFence_Endpoint_Config::clear_cache();
        self::load_endpoint_map();
    }
    
    /**
     * Match dynamic endpoint with path parameters
     * 
     * Handles endpoints like yara/batch/{batch_id}/status by matching
     * actual paths like yara/batch/UUID/status to the pattern.
     * 
     * For dynamic endpoints, we cannot use obfuscated codes because
     * the UUID is unique per request. Return the original path.
     * 
     * @since 2.2.4 Added handling for SIEM Phase 3.5 dynamic endpoints
     * @param string $endpoint_path Actual endpoint path to match
     * @return string|null Original path if it's a known dynamic pattern, null otherwise
     */
    private static function match_dynamic_endpoint($endpoint_path) {
        if (empty(self::$endpoint_map)) {
            return null;
        }
        
        // Common dynamic endpoint patterns
        $patterns = array(
            // YARA batch endpoints with UUID parameter
            '#^yara/batch/[a-f0-9-]{36}/status$#' => 'yara/batch/{batch_id}/status',
            '#^yara/batch/[a-f0-9-]{36}$#' => 'yara/batch/{batch_id}',
            
            // Auth license status with license key parameter
            '#^auth/license_status/[A-Z0-9-]+$#' => 'auth/license_status',
            
            // SIEM Phase 3.5: Sites scan/settings/commands with UUID parameter
            '#^sites/[a-f0-9-]{36}/scan/progress$#' => 'sites/{site_uuid}/scan/progress',
            '#^sites/[a-f0-9-]{36}/scan/results$#' => 'sites/{site_uuid}/scan/results',
            '#^sites/[a-f0-9-]{36}/settings/sync$#' => 'sites/{site_uuid}/settings/sync',
            '#^sites/[a-f0-9-]{36}/pending-commands$#' => 'sites/{site_uuid}/pending-commands',
            '#^sites/[a-f0-9-]{36}/commands/\d+/acknowledge$#' => 'sites/{site_uuid}/commands/{command_id}/acknowledge',
            '#^sites/[a-f0-9-]{36}/commands/\d+/complete$#' => 'sites/{site_uuid}/commands/{command_id}/complete',
        );
        
        foreach ($patterns as $regex => $template) {
            if (preg_match($regex, $endpoint_path)) {
                // Check if we have a template registered for this pattern
                if (!empty(self::$endpoint_map[$template])) {
                    // For dynamic endpoints, we must use the actual path with UUID
                    // Cannot obfuscate because UUID changes per request
                    return $endpoint_path;
                }
            }
        }
        
        return null;
    }
    
    /**
     * Check if endpoint mapping is active
     * 
     * @return bool True if using obfuscated codes, false if using fallback
     */
    public static function is_mapping_active() {
        if (self::$endpoint_map === null) {
            self::load_endpoint_map();
        }
        
        return !empty(self::$endpoint_map);
    }
    
    /**
     * Get all mapped endpoints (for debugging)
     * 
     * @return array Map of logical names to codes (empty if server unreachable)
     */
    public static function get_all_endpoints() {
        if (self::$endpoint_map === null) {
            self::load_endpoint_map();
        }
        
        return self::$endpoint_map;
    }
}
