//! Content-agnostic caching system
//!
//! Provides generic caching for different content types with optimized
//! strategies for routes, i18n, and dynamic content types.

use std::collections::HashMap;
use std::sync::Arc;

use serde::{Deserialize, Serialize};

use super::memory::MemoryCache;
use super::persistent::PersistentCache;
use super::{estimate_size, Cache, CacheConfig, CacheEntry, CacheStats, ContentCacheKey};
use crate::routing::components::{RouteConfigToml, RoutesConfig};

/// Type alias for i18n cache entry with metadata
pub type I18nCacheEntryWithMetadata = (
    Arc<HashMap<String, String>>,
    CacheEntry<HashMap<String, String>>,
);

/// High-level content cache that manages different content types
#[derive(Clone, Debug)]
pub struct ContentCache {
    /// Routes cache
    routes_cache: Arc<MemoryCache<String, RoutesConfig>>,
    /// I18n content cache
    i18n_cache: Arc<MemoryCache<ContentCacheKey, HashMap<String, String>>>,
    /// Generic content cache for all content items
    generic_cache: Arc<MemoryCache<ContentCacheKey, serde_json::Value>>,
    /// Optional persistent cache for disk-based storage
    persistent_cache: Option<Arc<std::sync::Mutex<PersistentCache>>>,
    /// Cache configuration
    config: CacheConfig,
}

// Using unified content system - cache-optimized content item
pub use crate::content::UnifiedContentItem as ContentItem;

/// Content manifest for tracking available content
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentManifest {
    pub languages: Vec<String>,
    pub content_types: Vec<String>,
    pub last_updated: u64,
    pub version: String,
}

/// Alias for backwards compatibility
pub type ContentCacheStatistics = CacheStats;

/// Cache warmer for preloading content
#[derive(Debug, Default)]
pub struct CacheWarmer;

impl CacheWarmer {
    pub fn new() -> Self {
        Self
    }

    pub fn warm_content_cache(&self, _cache: &ContentCache) {
        // Generic cache warmer - implementation would depend on specific needs
        tracing::debug!("Cache warmer called - using generic implementation");
    }

    pub async fn warm_essential_content(&self) {
        // Generic cache warmer for essential content - implementation would depend on
        // specific needs
        tracing::debug!("Essential content warming called - using generic implementation");
    }
}

impl ContentCache {
    /// Create a new content cache with default configuration
    pub fn new() -> Self {
        let config = CacheConfig::default();
        Self::with_config(config)
    }

    /// Create a new content cache with custom configuration
    pub fn with_config(config: CacheConfig) -> Self {
        let cache_dir = config.persistent_cache_dir.clone();
        Self::with_config_and_cache_dir(config, cache_dir)
    }

    /// Create a new content cache with custom configuration and optional cache
    /// directory
    pub fn with_config_and_cache_dir(
        config: CacheConfig,
        cache_dir: Option<std::path::PathBuf>,
    ) -> Self {
        // Create specific cache configurations for different content types
        let mut routes_config = config.clone();
        routes_config.max_entries = 50;
        routes_config
            .content_type_ttl
            .insert("routes".to_string(), 3600);

        let mut i18n_config = config.clone();
        i18n_config.max_entries = 200;
        i18n_config
            .content_type_ttl
            .insert("i18n".to_string(), 1800);

        let mut content_config = config.clone();
        content_config.max_entries = 500; // Content items

        // Set up TTL for all available content types dynamically
        let available_content_types = crate::get_all_content_types();
        for content_type in available_content_types {
            content_config
                .content_type_ttl
                .insert(content_type.to_string(), 900);
        }

        // Create persistent cache if enabled and cache directory is provided
        let persistent_cache = if config.enable_persistent_cache {
            if let Some(cache_dir) = cache_dir {
                match PersistentCache::with_config(cache_dir, config.clone()) {
                    Ok(persistent) => {
                        tracing::info!("Persistent cache initialized successfully");
                        Some(Arc::new(std::sync::Mutex::new(persistent)))
                    }
                    Err(e) => {
                        tracing::error!("Failed to initialize persistent cache: {}", e);
                        None
                    }
                }
            } else {
                tracing::warn!("Persistent cache enabled but no cache directory provided");
                None
            }
        } else {
            None
        };

        Self {
            routes_cache: Arc::new(MemoryCache::with_config(routes_config)),
            i18n_cache: Arc::new(MemoryCache::with_config(i18n_config)),
            generic_cache: Arc::new(MemoryCache::with_config(content_config)),
            persistent_cache,
            config,
        }
    }

    /// Cache a route configuration
    pub fn cache_routes(&self, key: String, routes: RoutesConfig, version: String) {
        self.routes_cache
            .put(key.clone(), routes, "routes".to_string(), version);
        tracing::debug!("Cached routes configuration: {}", key);
    }

    /// Get cached routes
    pub fn get_routes(&self, key: &str) -> Option<Arc<RoutesConfig>> {
        self.routes_cache
            .get(&key.to_string())
            .map(|entry| entry.data)
    }

    /// Cache i18n content
    pub fn cache_i18n(
        &self,
        key: ContentCacheKey,
        content: HashMap<String, String>,
        version: String,
    ) {
        let size = estimate_size(&content);
        self.i18n_cache
            .put(key, content, "i18n".to_string(), version);
        tracing::debug!("Cached i18n content ({} bytes)", size);
    }

    /// Get cached i18n content
    pub fn get_i18n(&self, key: &ContentCacheKey) -> Option<Arc<HashMap<String, String>>> {
        self.i18n_cache.get(key).map(|entry| entry.data)
    }

    /// Check if routes cache contains a key
    pub fn contains_routes(&self, key: &str) -> bool {
        self.routes_cache.get(&key.to_string()).is_some()
    }

    /// Check if i18n cache contains a key
    pub fn contains_i18n(&self, key: &ContentCacheKey) -> bool {
        self.i18n_cache.get(key).is_some()
    }

    /// Get routes cache entry with metadata
    pub fn get_routes_with_metadata(
        &self,
        key: &str,
    ) -> Option<(Arc<RoutesConfig>, CacheEntry<RoutesConfig>)> {
        if let Some(entry) = self.routes_cache.get(&key.to_string()) {
            Some((entry.data.clone(), entry))
        } else {
            None
        }
    }

    /// Get i18n cache entry with metadata
    pub fn get_i18n_with_metadata(
        &self,
        key: &ContentCacheKey,
    ) -> Option<I18nCacheEntryWithMetadata> {
        if let Some(entry) = self.i18n_cache.get(key) {
            Some((entry.data.clone(), entry))
        } else {
            None
        }
    }

    /// Get content from generic cache with a key
    pub fn get_content(&self, key: &ContentCacheKey) -> Option<Arc<serde_json::Value>> {
        self.generic_cache.get(key).map(|entry| entry.data)
    }

    /// Cache generic content
    pub fn cache_content<T: Serialize>(&self, key: ContentCacheKey, content: T, version: String) {
        let json_value = serde_json::to_value(&content).unwrap_or(serde_json::Value::Null);
        let size = estimate_size(&json_value);
        let content_type = key.content_type.clone();

        self.generic_cache
            .put(key, json_value, content_type.clone(), version);
        tracing::debug!("Cached {} content ({} bytes)", content_type, size);
    }

    /// Get total cache statistics
    pub fn stats(&self) -> CacheStats {
        let routes_stats = self.routes_cache.stats();
        let i18n_stats = self.i18n_cache.stats();
        let generic_stats = self.generic_cache.stats();

        CacheStats {
            total_entries: routes_stats.total_entries
                + i18n_stats.total_entries
                + generic_stats.total_entries,
            total_hits: routes_stats.total_hits + i18n_stats.total_hits + generic_stats.total_hits,
            total_misses: routes_stats.total_misses
                + i18n_stats.total_misses
                + generic_stats.total_misses,
            hit_ratio: {
                let total_requests = routes_stats.total_hits
                    + routes_stats.total_misses
                    + i18n_stats.total_hits
                    + i18n_stats.total_misses
                    + generic_stats.total_hits
                    + generic_stats.total_misses;
                let total_hits =
                    routes_stats.total_hits + i18n_stats.total_hits + generic_stats.total_hits;
                if total_requests > 0 {
                    total_hits as f64 / total_requests as f64
                } else {
                    0.0
                }
            },
            total_size_bytes: routes_stats.total_size_bytes
                + i18n_stats.total_size_bytes
                + generic_stats.total_size_bytes,
            avg_age_seconds: (routes_stats.avg_age_seconds
                + i18n_stats.avg_age_seconds
                + generic_stats.avg_age_seconds)
                / 3.0,
            entries_by_language: {
                let mut combined = routes_stats.entries_by_language;
                for (k, v) in i18n_stats.entries_by_language {
                    *combined.entry(k).or_insert(0) += v;
                }
                for (k, v) in generic_stats.entries_by_language {
                    *combined.entry(k).or_insert(0) += v;
                }
                combined
            },
            utilization_percent: (routes_stats.utilization_percent
                + i18n_stats.utilization_percent
                + generic_stats.utilization_percent)
                / 3.0,
            most_accessed: {
                let mut combined = routes_stats.most_accessed;
                combined.extend(i18n_stats.most_accessed);
                combined.extend(generic_stats.most_accessed);
                combined.sort_by(|a, b| b.1.cmp(&a.1));
                combined.truncate(10); // Keep top 10
                combined
            },
            entries_by_type: {
                let mut combined = routes_stats.entries_by_type;
                for (k, v) in i18n_stats.entries_by_type {
                    *combined.entry(k).or_insert(0) += v;
                }
                for (k, v) in generic_stats.entries_by_type {
                    *combined.entry(k).or_insert(0) += v;
                }
                combined
            },
        }
    }

    /// Clear all caches
    pub fn clear_all(&self) {
        self.routes_cache.clear();
        self.i18n_cache.clear();
        self.generic_cache.clear();

        tracing::info!("Cleared all caches");
    }

    /// Clean up expired entries in all caches
    pub fn cleanup_expired(&self) {
        self.routes_cache.cleanup_expired();
        self.i18n_cache.cleanup_expired();
        self.generic_cache.cleanup_expired();

        tracing::debug!("Cleaned up expired entries in all caches");
    }

    /// Invalidate cache for a specific content type
    pub fn invalidate_content_type(&self, content_type: &str) {
        match content_type {
            "routes" => self.routes_cache.clear(),
            "i18n" => self.i18n_cache.clear(),
            _ => {
                // For any other content types, check if they exist in available types
                let available_content_types = crate::get_all_content_types();
                if available_content_types.iter().any(|ct| *ct == content_type) {
                    // For generic cache, we'd need to iterate and remove matching types
                    tracing::warn!(
                        "Cannot invalidate content type '{}' from generic cache - not implemented",
                        content_type
                    );
                } else {
                    tracing::warn!(
                        "Unknown content type '{}' for cache invalidation",
                        content_type
                    );
                }
            }
        }

        tracing::info!("Invalidated cache for content type: {}", content_type);
    }

    /// Get cache configuration
    pub fn get_config(&self) -> &CacheConfig {
        &self.config
    }

    /// Check if persistent cache is available
    pub fn has_persistent_cache(&self) -> bool {
        self.persistent_cache.is_some()
    }

    /// Write to persistent cache if available
    pub fn persist_content<T: Serialize>(&self, key: &str, content: &T) -> Result<(), String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(mut cache) => {
                    match cache.put(key, content, "persistent".to_string(), "v1".to_string()) {
                        Ok(_) => {
                            tracing::debug!("Successfully persisted content with key: {}", key);
                            Ok(())
                        }
                        Err(e) => Err(format!("Failed to write to persistent cache: {}", e)),
                    }
                }
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Read from persistent cache if available
    pub fn read_persisted_content<T: for<'de> Deserialize<'de>>(
        &self,
        key: &str,
    ) -> Result<Option<T>, String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(mut cache) => match cache.get::<T>(key) {
                    Ok(content) => {
                        if content.is_some() {
                            tracing::debug!(
                                "Successfully read persisted content with key: {}",
                                key
                            );
                        }
                        Ok(content)
                    }
                    Err(e) => Err(format!("Failed to read from persistent cache: {}", e)),
                },
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Preload content cache for given languages (backward compatibility
    /// method)
    pub fn preload_for_languages(&self, languages: Vec<String>) {
        tracing::debug!("Preloading cache for languages: {:?}", languages);
        // Generic implementation - would need specific content loading logic
    }

    /// Get cache statistics (alias for backward compatibility)
    pub fn get_stats(&self) -> CacheStats {
        self.stats()
    }

    /// Remove content from persistent cache if available
    pub fn remove_persisted_content(&self, key: &str) -> Result<bool, String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(mut cache) => match cache.remove(key) {
                    Ok(removed) => {
                        if removed {
                            tracing::debug!(
                                "Successfully removed persisted content with key: {}",
                                key
                            );
                        }
                        Ok(removed)
                    }
                    Err(e) => Err(format!("Failed to remove from persistent cache: {}", e)),
                },
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Clear persistent cache if available
    pub fn clear_persistent_cache(&self) -> Result<(), String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(mut cache) => match cache.clear() {
                    Ok(_) => {
                        tracing::debug!("Successfully cleared persistent cache");
                        Ok(())
                    }
                    Err(e) => Err(format!("Failed to clear persistent cache: {}", e)),
                },
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Cleanup expired entries in persistent cache
    pub fn cleanup_persistent_cache(&self) -> Result<(), String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(mut cache) => match cache.cleanup_expired() {
                    Ok(_) => {
                        tracing::debug!("Successfully cleaned up expired persistent cache entries");
                        Ok(())
                    }
                    Err(e) => Err(format!("Failed to cleanup persistent cache: {}", e)),
                },
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Get persistent cache size information
    pub fn get_persistent_cache_info(&self) -> Result<(usize, usize), String> {
        if let Some(persistent_cache) = &self.persistent_cache {
            match persistent_cache.lock() {
                Ok(cache) => {
                    let entries = cache.len();
                    let size_bytes = cache.total_size_bytes();
                    tracing::debug!(
                        "Persistent cache info: {} entries, {} bytes",
                        entries,
                        size_bytes
                    );
                    Ok((entries, size_bytes))
                }
                Err(_) => Err("Failed to acquire persistent cache lock".to_string()),
            }
        } else {
            Err("Persistent cache not available".to_string())
        }
    }

    /// Check if all caches are empty
    pub fn is_empty(&self) -> bool {
        let routes_stats = self.routes_cache.stats();
        let i18n_stats = self.i18n_cache.stats();
        let generic_stats = self.generic_cache.stats();

        routes_stats.total_entries == 0
            && i18n_stats.total_entries == 0
            && generic_stats.total_entries == 0
    }
}

impl Default for ContentCache {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_content_cache_creation() {
        let cache = ContentCache::new();
        assert_eq!(cache.stats().total_entries, 0);
    }

    #[test]
    fn test_routes_cache() {
        let cache = ContentCache::new();
        let routes = RoutesConfig { routes: vec![] };

        cache.cache_routes("test".to_string(), routes.clone(), "v1".to_string());
        assert!(cache.contains_routes("test"));

        let cached_routes = cache.get_routes("test").unwrap();
        assert_eq!(cached_routes.routes.len(), routes.routes.len());
    }

    #[test]
    fn test_generic_content_cache() {
        let cache = ContentCache::new();
        let key = ContentCacheKey::new("content", "test-1").with_language("en".to_string());
        let content = serde_json::json!({"title": "Test", "content": "Test content"});

        cache.cache_content(key.clone(), content.clone(), "v1".to_string());

        let cached = cache.get_content(&key).unwrap();
        assert_eq!(*cached, content);
    }

    #[test]
    fn test_cache_stats() {
        let cache = ContentCache::new();
        let key = ContentCacheKey::new("content", "test-1").with_language("en".to_string());

        cache.cache_content(key, serde_json::json!({"test": true}), "v1".to_string());

        let stats = cache.stats();
        assert_eq!(stats.total_entries, 1);
    }
}
