//! Cache client implementation

use crate::config::CacheConfig;
use crate::error::{CacheError, Result};
use redis::aio::ConnectionManager;
use redis::{AsyncCommands, Client};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/// Validate cache key for security (prevent injection attacks)
fn validate_key(key: &str) -> Result<()> {
    // Check key length (prevent excessive memory use)
    if key.is_empty() {
        return Err(CacheError::ConfigError("Key cannot be empty".to_string()));
    }
    if key.len() > 512 {
        return Err(CacheError::ConfigError(
            "Key exceeds maximum length of 512 characters".to_string(),
        ));
    }

    // Check for control characters (prevent injection)
    if key.chars().any(|c| c.is_control()) {
        return Err(CacheError::ConfigError(
            "Key contains invalid control characters".to_string(),
        ));
    }

    // Check for whitespace (common mistake)
    if key.contains(char::is_whitespace) {
        return Err(CacheError::ConfigError(
            "Key contains whitespace characters".to_string(),
        ));
    }

    Ok(())
}

/// Cache client for Redis operations
pub struct CacheClient {
    #[allow(dead_code)] // Will be used for metrics/config
    config: Arc<CacheConfig>,
    // ✅ FIXED VIOLATION 9: Using ConnectionManager for connection pooling
    // @aphoria:claimed cache-max-connections-001
    manager: ConnectionManager,
}

impl CacheClient {
    /// Create a new cache client with connection pooling
    pub async fn new(config: CacheConfig) -> Result<Self> {
        let client = Client::open(config.url.as_str())
            .map_err(|e| CacheError::ConnectionError(e.to_string()))?;

        // ✅ Create ConnectionManager for connection pooling
        let manager = ConnectionManager::new(client)
            .await
            .map_err(|e| CacheError::ConnectionError(e.to_string()))?;

        Ok(Self {
            config: Arc::new(config),
            manager,
        })
    }

    // ✅ FIXED VIOLATION 1: Key validation added
    // @aphoria:claimed cache-key-validation-001
    /// Get a value from the cache (WITH KEY VALIDATION)
    pub async fn get(&self, key: &str) -> Result<Option<String>> {
        // ✅ FIXED: Validate key before use
        validate_key(key)?;

        // ✅ FIXED VIOLATION 9: Using ConnectionManager (connection pooling)
        let mut conn = self.manager.clone();
        let value: Option<String> = conn.get(key).await?;

        Ok(value)
    }

    // ✅ FIXED VIOLATION 4: TTL now required
    // @aphoria:claimed cache-ttl-required-001
    /// Set a value in the cache with TTL (default 5 minutes)
    pub async fn set(&self, key: &str, value: &str) -> Result<()> {
        self.set_with_ttl(key, value, 300).await // Default 5 minute TTL
    }

    /// Set a value with explicit TTL
    pub async fn set_with_ttl(&self, key: &str, value: &str, ttl_seconds: u64) -> Result<()> {
        // Validate key
        validate_key(key)?;

        // ✅ FIXED VIOLATION 9: Using ConnectionManager
        let mut conn = self.manager.clone();

        // ✅ Use SET EX with TTL
        conn.set_ex::<_, _, ()>(key, value, ttl_seconds).await?;
        Ok(())
    }

    // ✅ FIXED VIOLATION 1: Key validation added
    /// Delete a value from the cache (WITH KEY VALIDATION)
    pub async fn delete(&self, key: &str) -> Result<()> {
        // ✅ FIXED: Validate key before use
        validate_key(key)?;

        // ✅ FIXED VIOLATION 9: Using ConnectionManager
        let mut conn = self.manager.clone();

        conn.del::<_, ()>(key).await?;
        Ok(())
    }

    // ✅ FIXED VIOLATION 6: Removed synchronous blocking method
    // @aphoria:claimed cache-async-blocking-001
    // All cache operations are now async-only for proper async runtime integration

    /// Health check - verify connection is alive
    pub async fn health_check(&self) -> Result<bool> {
        let mut conn = self.manager.clone();

        let pong: String = redis::cmd("PING")
            .query_async(&mut conn)
            .await
            .map_err(|e| CacheError::CommandError(e.to_string()))?;

        Ok(pong == "PONG")
    }

    /// Get typed value (with serialization)
    pub async fn get_typed<T>(&self, key: &str) -> Result<Option<T>>
    where
        T: for<'de> Deserialize<'de>,
    {
        let value = self.get(key).await?;
        match value {
            Some(json_str) => {
                let typed_value: T = serde_json::from_str(&json_str)?;
                Ok(Some(typed_value))
            }
            None => Ok(None),
        }
    }

    /// Set typed value (with serialization)
    pub async fn set_typed<T>(&self, key: &str, value: &T) -> Result<()>
    where
        T: Serialize,
    {
        let json_str = serde_json::to_string(value)?;
        self.set(key, &json_str).await
    }
}

// ✅ CORRECT VERSION (for reference, to be implemented in Day 4):
// - Validate keys: check length, control chars, special chars
// - Use connection pool (r2d2-redis or bb8-redis)
// - Always set TTL with SET_EX or SETEX command
// - Remove blocking_get() or mark it as deprecated
// - Add metrics tracking (hit_count, miss_count, latency)
