//! Caching layer for apt-ostree performance optimization
//! 
//! This module provides intelligent caching strategies for frequently accessed
//! data including package metadata, OSTree commits, and system information.

use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use serde::{Serialize, Deserialize};
use tracing::{debug, info};

/// Cache entry with expiration
#[derive(Debug, Clone)]
pub struct CacheEntry<T> {
    pub data: T,
    pub created_at: Instant,
    pub expires_at: Instant,
    pub access_count: u64,
    pub last_accessed: Instant,
}

impl<T> CacheEntry<T> {
    /// Create a new cache entry
    pub fn new(data: T, ttl: Duration) -> Self {
        let now = Instant::now();
        Self {
            data,
            created_at: now,
            expires_at: now + ttl,
            access_count: 0,
            last_accessed: now,
        }
    }
    
    /// Check if the entry has expired
    pub fn is_expired(&self) -> bool {
        Instant::now() > self.expires_at
    }
    
    /// Mark the entry as accessed
    pub fn mark_accessed(&mut self) {
        self.access_count += 1;
        self.last_accessed = Instant::now();
    }
    
    /// Get the age of the entry
    pub fn age(&self) -> Duration {
        self.created_at.elapsed()
    }
    
    /// Get the time since last access
    pub fn time_since_last_access(&self) -> Duration {
        self.last_accessed.elapsed()
    }
}

/// LRU Cache implementation with TTL support
pub struct LruCache<K, V> {
    capacity: usize,
    cache: HashMap<K, CacheEntry<V>>,
    access_order: Vec<K>,
}

impl<K, V> LruCache<K, V>
where
    K: Clone + Eq + std::hash::Hash,
    V: Clone,
{
    /// Create a new LRU cache with the specified capacity
    pub fn new(capacity: usize) -> Self {
        Self {
            capacity,
            cache: HashMap::with_capacity(capacity),
            access_order: Vec::with_capacity(capacity),
        }
    }
    
    /// Get a value from the cache
    pub fn get(&mut self, key: &K) -> Option<&V> {
        // First, check if the key exists and get a reference to check expiration
        let entry_ref = self.cache.get(key)?;
        
        // Check if entry is expired
        let is_expired = entry_ref.is_expired();
        
        // If expired, remove it and return None
        if is_expired {
            self.remove(key);
            return None;
        }
        
        // Update access order first (move to most recently used)
        self.update_access_order(key);
        
        // Now get mutable access to update access info
        if let Some(entry) = self.cache.get_mut(key) {
            // Mark as accessed
            entry.mark_accessed();
            
            // Return reference to data
            Some(&entry.data)
        } else {
            None
        }
    }
    
    /// Insert a value into the cache
    pub fn insert(&mut self, key: K, value: V, ttl: Duration) {
        // Remove expired entries first
        self.cleanup_expired();
        
        // If cache is full, remove least recently used entry
        if self.cache.len() >= self.capacity {
            if let Some(lru_key) = self.access_order.first().cloned() {
                self.remove(&lru_key);
            }
        }
        
        // Insert new entry
        let entry = CacheEntry::new(value, ttl);
        self.cache.insert(key.clone(), entry);
        self.access_order.push(key);
    }
    
    /// Remove a key from the cache
    pub fn remove(&mut self, key: &K) -> Option<V> {
        if let Some(entry) = self.cache.remove(key) {
            // Remove from access order
            if let Some(pos) = self.access_order.iter().position(|k| k == key) {
                self.access_order.remove(pos);
            }
            Some(entry.data)
        } else {
            None
        }
    }
    
    /// Check if a key exists in the cache
    pub fn contains_key(&self, key: &K) -> bool {
        self.cache.contains_key(key)
    }
    
    /// Get the current size of the cache
    pub fn len(&self) -> usize {
        self.cache.len()
    }
    
    /// Check if the cache is empty
    pub fn is_empty(&self) -> bool {
        self.cache.is_empty()
    }
    
    /// Clear all entries from the cache
    pub fn clear(&mut self) {
        self.cache.clear();
        self.access_order.clear();
    }
    
    /// Get cache statistics
    pub fn stats(&self) -> CacheStats {
        let mut total_access_count = 0;
        let mut expired_count = 0;
        let mut total_age = Duration::ZERO;
        
        for entry in self.cache.values() {
            total_access_count += entry.access_count;
            total_age += entry.age();
            if entry.is_expired() {
                expired_count += 1;
            }
        }
        
        let avg_access_count = if self.cache.is_empty() {
            0.0
        } else {
            total_access_count as f64 / self.cache.len() as f64
        };
        
        let avg_age = if self.cache.is_empty() {
            Duration::ZERO
        } else {
            total_age / self.cache.len() as u32
        };
        
        CacheStats {
            total_entries: self.cache.len(),
            expired_entries: expired_count,
            total_access_count,
            average_access_count: avg_access_count,
            average_age: avg_age,
            capacity: self.capacity,
        }
    }
    
    /// Update the access order for a key
    fn update_access_order(&mut self, key: &K) {
        // Remove from current position
        if let Some(pos) = self.access_order.iter().position(|k| k == key) {
            self.access_order.remove(pos);
        }
        // Add to end (most recently used)
        self.access_order.push(key.clone());
    }
    
    /// Clean up expired entries
    fn cleanup_expired(&mut self) {
        let expired_keys: Vec<K> = self.cache
            .iter()
            .filter_map(|(key, entry)| {
                if entry.is_expired() {
                    Some(key.clone())
                } else {
                    None
                }
            })
            .collect();
        
        for key in expired_keys {
            self.remove(&key);
        }
    }
}

/// Cache statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStats {
    pub total_entries: usize,
    pub expired_entries: usize,
    pub total_access_count: u64,
    pub average_access_count: f64,
    pub average_age: Duration,
    pub capacity: usize,
}

/// Multi-level cache manager
pub struct CacheManager {
    package_cache: Arc<RwLock<LruCache<String, PackageInfo>>>,
    deployment_cache: Arc<RwLock<LruCache<String, DeploymentInfo>>>,
    system_info_cache: Arc<RwLock<LruCache<String, SystemInfo>>>,
    metadata_cache: Arc<RwLock<LruCache<String, MetadataInfo>>>,
}

impl CacheManager {
    /// Create a new cache manager
    pub fn new() -> Self {
        Self {
            package_cache: Arc::new(RwLock::new(LruCache::new(10000))), // 10k packages
            deployment_cache: Arc::new(RwLock::new(LruCache::new(100))), // 100 deployments
            system_info_cache: Arc::new(RwLock::new(LruCache::new(10))), // 10 system info entries
            metadata_cache: Arc::new(RwLock::new(LruCache::new(1000))), // 1k metadata entries
        }
    }
    
    /// Get package information from cache
    pub async fn get_package(&self, package_name: &str) -> Option<PackageInfo> {
        let mut cache = self.package_cache.write().unwrap();
        cache.get(&package_name.to_string()).cloned()
    }
    
    /// Cache package information
    pub async fn cache_package(&self, package_name: String, info: PackageInfo, ttl: Duration) {
        let mut cache = self.package_cache.write().unwrap();
        cache.insert(package_name, info, ttl);
    }
    
    /// Get deployment information from cache
    pub async fn get_deployment(&self, deployment_id: &str) -> Option<DeploymentInfo> {
        let mut cache = self.deployment_cache.write().unwrap();
        cache.get(&deployment_id.to_string()).cloned()
    }
    
    /// Cache deployment information
    pub async fn cache_deployment(&self, deployment_id: String, info: DeploymentInfo, ttl: Duration) {
        let mut cache = self.deployment_cache.write().unwrap();
        cache.insert(deployment_id, info, ttl);
    }
    
    /// Get system information from cache
    pub async fn get_system_info(&self, key: &str) -> Option<SystemInfo> {
        let mut cache = self.system_info_cache.write().unwrap();
        cache.get(&key.to_string()).cloned()
    }
    
    /// Cache system information
    pub async fn cache_system_info(&self, key: String, info: SystemInfo, ttl: Duration) {
        let mut cache = self.system_info_cache.write().unwrap();
        cache.insert(key, info, ttl);
    }
    
    /// Get metadata information from cache
    pub async fn get_metadata(&self, key: &str) -> Option<MetadataInfo> {
        let mut cache = self.metadata_cache.write().unwrap();
        cache.get(&key.to_string()).cloned()
    }
    
    /// Cache metadata information
    pub async fn cache_metadata(&self, key: String, info: MetadataInfo, ttl: Duration) {
        let mut cache = self.metadata_cache.write().unwrap();
        cache.insert(key, info, ttl);
    }
    
    /// Get cache statistics for all caches
    pub async fn get_stats(&self) -> CacheManagerStats {
        let package_stats = self.package_cache.read().unwrap().stats();
        let deployment_stats = self.deployment_cache.read().unwrap().stats();
        let system_info_stats = self.system_info_cache.read().unwrap().stats();
        let metadata_stats = self.metadata_cache.read().unwrap().stats();
        
        CacheManagerStats {
            package_cache: package_stats,
            deployment_cache: deployment_stats,
            system_info_cache: system_info_stats,
            metadata_cache: metadata_stats,
        }
    }
    
    /// Clear all caches
    pub async fn clear_all(&self) {
        info!("Clearing all caches");
        
        self.package_cache.write().unwrap().clear();
        self.deployment_cache.write().unwrap().clear();
        self.system_info_cache.write().unwrap().clear();
        self.metadata_cache.write().unwrap().clear();
        
        debug!("All caches cleared");
    }
    
    /// Clean up expired entries from all caches
    pub async fn cleanup_expired(&self) {
        debug!("Cleaning up expired cache entries");
        
        let mut package_cache = self.package_cache.write().unwrap();
        package_cache.cleanup_expired();
        
        let mut deployment_cache = self.deployment_cache.write().unwrap();
        deployment_cache.cleanup_expired();
        
        let mut system_info_cache = self.system_info_cache.write().unwrap();
        system_info_cache.cleanup_expired();
        
        let mut metadata_cache = self.metadata_cache.write().unwrap();
        metadata_cache.cleanup_expired();
        
        debug!("Cache cleanup completed");
    }
}

/// Cache manager statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheManagerStats {
    pub package_cache: CacheStats,
    pub deployment_cache: CacheStats,
    pub system_info_cache: CacheStats,
    pub metadata_cache: CacheStats,
}

// Placeholder types for demonstration
// These would be replaced with actual types from the codebase

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageInfo {
    pub name: String,
    pub version: String,
    pub architecture: String,
    pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentInfo {
    pub id: String,
    pub commit: String,
    pub is_current: bool,
    pub timestamp: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemInfo {
    pub os: String,
    pub kernel: String,
    pub architecture: String,
    pub kernel_cmdline: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataInfo {
    pub key: String,
    pub value: String,
    pub timestamp: String,
}

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

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_cache_entry_creation() {
        let data = "test data".to_string();
        let ttl = Duration::from_secs(60);
        let entry = CacheEntry::new(data.clone(), ttl);
        
        assert_eq!(entry.data, data);
        assert!(!entry.is_expired());
        assert_eq!(entry.access_count, 0);
    }
    
    #[test]
    fn test_cache_entry_expiration() {
        let data = "test data".to_string();
        let ttl = Duration::from_millis(1);
        let mut entry = CacheEntry::new(data, ttl);
        
        // Wait for expiration
        std::thread::sleep(Duration::from_millis(10));
        
        assert!(entry.is_expired());
    }
    
    #[test]
    fn test_lru_cache_basic_operations() {
        let mut cache = LruCache::new(3);
        
        // Insert entries
        cache.insert("key1".to_string(), "value1".to_string(), Duration::from_secs(60));
        cache.insert("key2".to_string(), "value2".to_string(), Duration::from_secs(60));
        cache.insert("key3".to_string(), "value3".to_string(), Duration::from_secs(60));
        
        assert_eq!(cache.len(), 3);
        
        // Access key1 to make it most recently used
        assert_eq!(cache.get(&"key1".to_string()), Some(&"value1".to_string()));
        
        // Insert one more to trigger LRU eviction
        // Since key1 was just accessed, key2 should be evicted (least recently used)
        cache.insert("key4".to_string(), "value4".to_string(), Duration::from_secs(60));
        
        assert_eq!(cache.len(), 3);
        assert_eq!(cache.get(&"key1".to_string()), Some(&"value1".to_string())); // Still there (most recently used)
        assert_eq!(cache.get(&"key2".to_string()), None); // Should be evicted (least recently used)
        assert_eq!(cache.get(&"key3".to_string()), Some(&"value3".to_string())); // Still there
        assert_eq!(cache.get(&"key4".to_string()), Some(&"value4".to_string())); // Newly inserted
    }
    
    #[test]
    fn test_cache_manager_creation() {
        let manager = CacheManager::new();
        let stats = futures::executor::block_on(manager.get_stats());
        
        assert_eq!(stats.package_cache.capacity, 10000);
        assert_eq!(stats.deployment_cache.capacity, 100);
        assert_eq!(stats.system_info_cache.capacity, 10);
        assert_eq!(stats.metadata_cache.capacity, 1000);
    }
}
