//! Enhanced Route Caching System
//!
//! This module implements a multi-layer caching system for route resolution:
//! - L1: Hot routes cache (HashMap) for frequently accessed routes
//! - L2: Session-specific LRU cache for user patterns
//! - L3: Static route mappings (existing ROUTE_MAPPINGS_CACHE)

use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};

use serde::{Deserialize, Serialize};

/// Configuration for the route cache
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteCacheConfig {
    pub enabled: bool,
    pub max_entries: usize,
    pub ttl_seconds: u64,
    pub hot_route_threshold: u32, // Hit count to promote to hot cache
}

impl Default for RouteCacheConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            max_entries: 1000,
            ttl_seconds: 3600, // 1 hour
            hot_route_threshold: 5,
        }
    }
}

/// Cached route entry with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedRoute {
    pub source_path: String,
    pub target_path: String,
    pub source_language: String,
    pub target_language: String,
    pub created_at: u64,
    pub hit_count: u32,
    pub last_accessed: u64,
}

impl CachedRoute {
    pub fn new(
        source_path: String,
        target_path: String,
        source_language: String,
        target_language: String,
    ) -> Self {
        let timestamp = {
            #[cfg(not(target_arch = "wasm32"))]
            {
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
            }
            #[cfg(target_arch = "wasm32")]
            {
                // Use a simple counter for WASM
                use std::sync::atomic::{AtomicU64, Ordering};
                static COUNTER: AtomicU64 = AtomicU64::new(0);
                COUNTER.fetch_add(1, Ordering::SeqCst)
            }
        };

        Self {
            source_path,
            target_path,
            source_language,
            target_language,
            created_at: timestamp,
            hit_count: 1,
            last_accessed: timestamp,
        }
    }

    pub fn hit(&mut self) {
        self.hit_count += 1;
        self.last_accessed = {
            #[cfg(not(target_arch = "wasm32"))]
            {
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
            }
            #[cfg(target_arch = "wasm32")]
            {
                self.last_accessed + 1
            }
        };
    }

    pub fn is_expired(&self, ttl_seconds: u64) -> bool {
        let current_time = {
            #[cfg(not(target_arch = "wasm32"))]
            {
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
            }
            #[cfg(target_arch = "wasm32")]
            {
                self.last_accessed + 1 // Simple increment for WASM
            }
        };

        current_time > self.created_at + ttl_seconds
    }
}

/// Cache statistics for monitoring and optimization
#[derive(Debug, Default, Clone)]
pub struct CacheStats {
    pub total_requests: u64,
    pub cache_hits: u64,
    pub cache_misses: u64,
    pub hot_cache_hits: u64,
    pub lru_cache_hits: u64,
    pub evictions: u64,
    pub expired_entries: u64,
}

impl CacheStats {
    pub fn hit_rate(&self) -> f64 {
        if self.total_requests == 0 {
            0.0
        } else {
            self.cache_hits as f64 / self.total_requests as f64
        }
    }
}

/// Multi-layer route cache
#[derive(Debug)]
pub struct RouteCache {
    config: RouteCacheConfig,

    // L1: Hot routes - frequently accessed routes
    hot_routes: HashMap<String, CachedRoute>,

    // L2: LRU cache for session-specific patterns
    lru_cache: HashMap<String, CachedRoute>,
    lru_order: Vec<String>, // Track access order for LRU eviction

    // Statistics
    stats: CacheStats,
}

impl RouteCache {
    pub fn new(config: RouteCacheConfig) -> Self {
        Self {
            config,
            hot_routes: HashMap::new(),
            lru_cache: HashMap::new(),
            lru_order: Vec::new(),
            stats: CacheStats::default(),
        }
    }

    /// Generate cache key from route parameters
    fn cache_key(source_path: &str, source_lang: &str, target_lang: &str) -> String {
        format!("{}|{}|{}", source_path, source_lang, target_lang)
    }

    /// Look up a route in the cache (simplified single-layer implementation)
    pub fn get(
        &mut self,
        source_path: &str,
        source_lang: &str,
        target_lang: &str,
    ) -> Option<String> {
        if !self.config.enabled {
            return None;
        }

        self.stats.total_requests += 1;
        let key = Self::cache_key(source_path, source_lang, target_lang);

        // First, check if the key exists and is not expired (separate scope for
        // immutable borrow)
        let cache_hit = {
            if let Some(cached) = self.lru_cache.get(&key) {
                !cached.is_expired(self.config.ttl_seconds)
            } else {
                false
            }
        };

        if cache_hit {
            // Now safely get mutable access to update the cache entry
            if let Some(cached) = self.lru_cache.get_mut(&key) {
                let target_path = cached.target_path.clone();
                let should_promote = cached.hit_count + 1 >= self.config.hot_route_threshold;

                // Update the cache entry
                cached.hit();

                // Update stats
                self.stats.cache_hits += 1;
                self.stats.lru_cache_hits += 1;

                // Update LRU order
                if let Some(pos) = self.lru_order.iter().position(|k| k == &key) {
                    self.lru_order.remove(pos);
                }
                self.lru_order.insert(0, key.clone());

                // Promote to hot cache if threshold reached
                if should_promote {
                    let promoted = cached.clone();
                    self.hot_routes.insert(key, promoted);
                }

                return Some(target_path);
            }
        }

        // Clean up expired entries (separate operation)
        self.cleanup_expired_entries();

        self.stats.cache_misses += 1;
        None
    }

    /// Helper method to clean up expired entries
    fn cleanup_expired_entries(&mut self) {
        let mut keys_to_remove = Vec::new();

        // Collect expired keys first
        for (k, cached) in &self.lru_cache {
            if cached.is_expired(self.config.ttl_seconds) {
                keys_to_remove.push(k.clone());
            }
        }

        // Remove expired entries
        for k in keys_to_remove {
            self.lru_cache.remove(&k);
            if let Some(pos) = self.lru_order.iter().position(|order_key| order_key == &k) {
                self.lru_order.remove(pos);
            }
            self.stats.expired_entries += 1;
        }
    }

    /// Store a route in the cache
    pub fn set(
        &mut self,
        source_path: &str,
        source_lang: &str,
        target_lang: &str,
        target_path: String,
    ) {
        if !self.config.enabled {
            return;
        }

        let key = Self::cache_key(source_path, source_lang, target_lang);
        let cached_route = CachedRoute::new(
            source_path.to_string(),
            target_path,
            source_lang.to_string(),
            target_lang.to_string(),
        );

        // Add to LRU cache first
        self.lru_cache.insert(key.clone(), cached_route);
        self.lru_order.insert(0, key);

        // Enforce size limits with LRU eviction
        while self.lru_cache.len() > self.config.max_entries {
            if let Some(oldest_key) = self.lru_order.pop() {
                self.lru_cache.remove(&oldest_key);
                self.stats.evictions += 1;
            }
        }
    }

    /// Get cache statistics
    pub fn get_stats(&self) -> CacheStats {
        self.stats.clone()
    }

    /// Clear all cached entries
    pub fn clear(&mut self) {
        self.hot_routes.clear();
        self.lru_cache.clear();
        self.lru_order.clear();
        self.stats = CacheStats::default();
    }

    /// Remove expired entries (maintenance operation)
    pub fn cleanup_expired(&mut self) -> usize {
        let mut removed = 0;

        // Clean hot routes
        let mut expired_hot: Vec<String> = Vec::new();
        for (key, cached) in &self.hot_routes {
            if cached.is_expired(self.config.ttl_seconds) {
                expired_hot.push(key.clone());
            }
        }
        for key in expired_hot {
            self.hot_routes.remove(&key);
            removed += 1;
        }

        // Clean LRU cache
        let mut expired_lru: Vec<String> = Vec::new();
        for (key, cached) in &self.lru_cache {
            if cached.is_expired(self.config.ttl_seconds) {
                expired_lru.push(key.clone());
            }
        }
        for key in &expired_lru {
            self.lru_cache.remove(key);
            if let Some(pos) = self.lru_order.iter().position(|k| k == key) {
                self.lru_order.remove(pos);
            }
            removed += 1;
        }

        self.stats.expired_entries += removed as u64;
        removed
    }
}

/// Global route cache instance
static ROUTE_CACHE: OnceLock<Arc<Mutex<RouteCache>>> = OnceLock::new();

/// Initialize the route cache
pub fn init_route_cache(config: RouteCacheConfig) {
    let cache = RouteCache::new(config);
    let _ = ROUTE_CACHE.set(Arc::new(Mutex::new(cache)));
}

/// Get the global route cache instance
pub fn get_route_cache() -> Option<Arc<Mutex<RouteCache>>> {
    ROUTE_CACHE.get().cloned()
}

/// Convenience function to cache a route lookup
pub fn cached_route_lookup<F>(
    source_path: &str,
    source_lang: &str,
    target_lang: &str,
    resolver: F,
) -> String
where
    F: FnOnce() -> String,
{
    if let Some(cache) = get_route_cache() {
        if let Ok(mut cache) = cache.lock() {
            // Try cache first
            if let Some(cached_result) = cache.get(source_path, source_lang, target_lang) {
                return cached_result;
            }

            // Cache miss - resolve and store
            let result = resolver();
            cache.set(source_path, source_lang, target_lang, result.clone());
            return result;
        }
    }

    // Fallback to direct resolution if cache unavailable
    resolver()
}
