use std::num::NonZeroUsize;
use std::sync::Arc;

use chrono::{DateTime, Duration, Utc};
use lru::LruCache;
use parking_lot::Mutex;

use crate::models::Extension;

/// Cached item with expiration
#[derive(Debug, Clone)]
struct CachedItem<T> {
    value: T,
    expires_at: DateTime<Utc>,
}

impl<T> CachedItem<T> {
    fn new(value: T, ttl: Duration) -> Self {
        Self {
            value,
            expires_at: Utc::now() + ttl,
        }
    }

    fn is_expired(&self) -> bool {
        Utc::now() > self.expires_at
    }
}

/// Extension cache with LRU eviction and TTL
pub struct ExtensionCache {
    list_cache: Arc<Mutex<LruCache<String, CachedItem<Vec<Extension>>>>>,
    metadata_cache: Arc<Mutex<LruCache<String, CachedItem<Extension>>>>,
    version_cache: Arc<Mutex<LruCache<String, CachedItem<Vec<String>>>>>,
    ttl: Duration,
    enable_metadata: bool,
    enable_list: bool,
}

impl ExtensionCache {
    /// Create new cache with specified capacity and TTL
    pub fn new(
        capacity: usize,
        ttl_seconds: u64,
        enable_metadata: bool,
        enable_list: bool,
    ) -> Self {
        let capacity = NonZeroUsize::new(capacity).expect("Capacity must be non-zero");

        Self {
            list_cache: Arc::new(Mutex::new(LruCache::new(capacity))),
            metadata_cache: Arc::new(Mutex::new(LruCache::new(capacity))),
            version_cache: Arc::new(Mutex::new(LruCache::new(capacity))),
            ttl: Duration::seconds(ttl_seconds as i64),
            enable_metadata,
            enable_list,
        }
    }

    /// Get cached extension list
    pub fn get_list(&self, key: &str) -> Option<Vec<Extension>> {
        if !self.enable_list {
            return None;
        }

        let mut cache = self.list_cache.lock();
        if let Some(item) = cache.get(key) {
            if !item.is_expired() {
                return Some(item.value.clone());
            } else {
                cache.pop(key);
            }
        }
        None
    }

    /// Put extension list in cache
    pub fn put_list(&self, key: String, value: Vec<Extension>) {
        if !self.enable_list {
            return;
        }

        let mut cache = self.list_cache.lock();
        cache.put(key, CachedItem::new(value, self.ttl));
    }

    /// Get cached extension metadata
    pub fn get_metadata(&self, key: &str) -> Option<Extension> {
        if !self.enable_metadata {
            return None;
        }

        let mut cache = self.metadata_cache.lock();
        if let Some(item) = cache.get(key) {
            if !item.is_expired() {
                return Some(item.value.clone());
            } else {
                cache.pop(key);
            }
        }
        None
    }

    /// Put extension metadata in cache
    pub fn put_metadata(&self, key: String, value: Extension) {
        if !self.enable_metadata {
            return;
        }

        let mut cache = self.metadata_cache.lock();
        cache.put(key, CachedItem::new(value, self.ttl));
    }

    /// Get cached version list
    pub fn get_versions(&self, key: &str) -> Option<Vec<String>> {
        let mut cache = self.version_cache.lock();
        if let Some(item) = cache.get(key) {
            if !item.is_expired() {
                return Some(item.value.clone());
            } else {
                cache.pop(key);
            }
        }
        None
    }

    /// Put version list in cache
    pub fn put_versions(&self, key: String, value: Vec<String>) {
        let mut cache = self.version_cache.lock();
        cache.put(key, CachedItem::new(value, self.ttl));
    }

    /// Invalidate specific cache entry
    pub fn invalidate(&self, key: &str) {
        {
            let mut cache = self.list_cache.lock();
            cache.pop(key);
        }
        {
            let mut cache = self.metadata_cache.lock();
            cache.pop(key);
        }
        {
            let mut cache = self.version_cache.lock();
            cache.pop(key);
        }
    }

    /// Invalidate all cache entries
    pub fn invalidate_all(&self) {
        {
            let mut cache = self.list_cache.lock();
            cache.clear();
        }
        {
            let mut cache = self.metadata_cache.lock();
            cache.clear();
        }
        {
            let mut cache = self.version_cache.lock();
            cache.clear();
        }
    }

    /// Get cache statistics
    pub fn stats(&self) -> CacheStats {
        let list_size = self.list_cache.lock().len();
        let metadata_size = self.metadata_cache.lock().len();
        let version_size = self.version_cache.lock().len();

        CacheStats {
            list_entries: list_size,
            metadata_entries: metadata_size,
            version_entries: version_size,
            total_entries: list_size + metadata_size + version_size,
        }
    }
}

/// Cache statistics
#[derive(Debug, Clone, serde::Serialize)]
pub struct CacheStats {
    pub list_entries: usize,
    pub metadata_entries: usize,
    pub version_entries: usize,
    pub total_entries: usize,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{ExtensionSource, ExtensionType};

    fn create_test_extension(name: &str) -> Extension {
        Extension {
            name: name.to_string(),
            extension_type: ExtensionType::Provider,
            version: "1.0.0".to_string(),
            description: "Test extension".to_string(),
            author: None,
            repository: None,
            source: ExtensionSource::Gitea,
            published_at: Utc::now(),
            download_url: None,
            checksum: None,
            size: None,
            tags: None,
        }
    }

    #[test]
    fn test_metadata_cache() {
        let cache = ExtensionCache::new(10, 300, true, true);
        let ext = create_test_extension("test");

        cache.put_metadata("test-key".to_string(), ext.clone());
        let cached = cache.get_metadata("test-key");

        assert!(cached.is_some());
        assert_eq!(cached.unwrap().name, ext.name);
    }

    #[test]
    fn test_list_cache() {
        let cache = ExtensionCache::new(10, 300, true, true);
        let extensions = vec![
            create_test_extension("test1"),
            create_test_extension("test2"),
        ];

        cache.put_list("test-list".to_string(), extensions.clone());
        let cached = cache.get_list("test-list");

        assert!(cached.is_some());
        assert_eq!(cached.unwrap().len(), extensions.len());
    }

    #[test]
    fn test_cache_invalidation() {
        let cache = ExtensionCache::new(10, 300, true, true);
        let ext = create_test_extension("test");

        cache.put_metadata("test-key".to_string(), ext);
        cache.invalidate("test-key");

        assert!(cache.get_metadata("test-key").is_none());
    }

    #[test]
    fn test_disabled_cache() {
        let cache = ExtensionCache::new(10, 300, false, false);
        let ext = create_test_extension("test");

        cache.put_metadata("test-key".to_string(), ext);
        assert!(cache.get_metadata("test-key").is_none());
    }
}
