use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::debug;
use bincode;

/// Cache key for tool calls
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct CacheKey {
    tool_id: String,
    parameters_hash: u64, // Hash of serialized parameters
    seed: u64,            // Include seed for deterministic caching
}

/// Cached tool result
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CachedResult {
    result: serde_json::Value,
    timestamp: chrono::DateTime<chrono::Utc>,
}

/// Tool result cache for deterministic caching
pub struct ToolCache {
    cache: Arc<RwLock<HashMap<CacheKey, CachedResult>>>,
    max_size: usize,
    ttl_seconds: Option<u64>,
}

impl ToolCache {
    pub fn new(max_size: usize, ttl_seconds: Option<u64>) -> Self {
        Self {
            cache: Arc::new(RwLock::new(HashMap::new())),
            max_size,
            ttl_seconds,
        }
    }
    
    /// Generate cache key from tool call
    fn generate_key(tool_id: &str, parameters: &serde_json::Value, seed: u64) -> CacheKey {
        // Hash parameters for cache key
        let params_bytes = bincode::serialize(parameters).unwrap_or_default();
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        use std::hash::{Hash, Hasher};
        params_bytes.hash(&mut hasher);
        let parameters_hash = hasher.finish();
        
        CacheKey {
            tool_id: tool_id.to_string(),
            parameters_hash,
            seed,
        }
    }
    
    /// Get cached result if available
    pub async fn get(
        &self,
        tool_id: &str,
        parameters: &serde_json::Value,
        seed: u64,
    ) -> Option<serde_json::Value> {
        let key = Self::generate_key(tool_id, parameters, seed);
        let cache = self.cache.read().await;
        
        if let Some(cached) = cache.get(&key) {
            // Check TTL if set
            if let Some(ttl) = self.ttl_seconds {
                let age = (chrono::Utc::now() - cached.timestamp).num_seconds();
                if age > ttl as i64 {
                    return None; // Expired
                }
            }
            
            debug!("Cache hit for tool: {}", tool_id);
            return Some(cached.result.clone());
        }
        
        None
    }
    
    /// Store result in cache
    pub async fn set(
        &self,
        tool_id: &str,
        parameters: &serde_json::Value,
        seed: u64,
        result: serde_json::Value,
    ) {
        let key = Self::generate_key(tool_id, parameters, seed);
        let mut cache = self.cache.write().await;
        
        // Evict if cache is full (simple FIFO-like eviction)
        if cache.len() >= self.max_size {
            // Remove oldest entry (simple approach: remove first)
            if let Some(oldest_key) = cache.keys().next().cloned() {
                cache.remove(&oldest_key);
            }
        }
        
        cache.insert(
            key,
            CachedResult {
                result,
                timestamp: chrono::Utc::now(),
            },
        );
        
        debug!("Cached result for tool: {}", tool_id);
    }
    
    /// Clear cache
    pub async fn clear(&self) {
        let mut cache = self.cache.write().await;
        cache.clear();
        debug!("Cleared tool cache");
    }
    
    /// Get cache statistics
    pub async fn stats(&self) -> CacheStats {
        let cache = self.cache.read().await;
        CacheStats {
            size: cache.len(),
            max_size: self.max_size,
            ttl_seconds: self.ttl_seconds,
        }
    }
}

/// Cache statistics
#[derive(Debug, Clone)]
pub struct CacheStats {
    pub size: usize,
    pub max_size: usize,
    pub ttl_seconds: Option<u64>,
}

impl Default for ToolCache {
    fn default() -> Self {
        Self::new(1000, None) // 1000 entries, no TTL
    }
}

