use anyhow::Result;
use redis::AsyncCommands;
use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::core::config::{cache as cache_config, REDIS_KEY_PREFIX};
use crate::core::db::RedisClient;

pub struct RedisCache {
    client: Arc<RwLock<RedisClient>>,
}

impl RedisCache {
    pub fn new() -> Result<Self> {
        let client = RedisClient::new()?;
        Ok(Self {
            client: Arc::new(RwLock::new(client)),
        })
    }

    fn prefixed_key(&self, key: &str) -> String {
        format!("{}cache:{}", REDIS_KEY_PREFIX.as_str(), key)
    }

    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let prefixed = self.prefixed_key(key);
        let value: Option<String> = conn.get(&prefixed).await?;

        match value {
            Some(json) => {
                let result = serde_json::from_str(&json)?;
                Ok(Some(result))
            }
            None => Ok(None),
        }
    }

    pub async fn set<T: Serialize>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let prefixed = self.prefixed_key(key);
        let json = serde_json::to_string(value)?;
        let _: String = conn.set_ex(&prefixed, json, ttl_secs).await?;
        Ok(())
    }

    pub async fn delete(&self, key: &str) -> Result<bool> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let prefixed = self.prefixed_key(key);
        let _: () = conn.del(&prefixed).await?;
        Ok(true)
    }

    pub async fn exists(&self, key: &str) -> Result<bool> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let prefixed = self.prefixed_key(key);
        let exists: bool = conn.exists(&prefixed).await?;
        Ok(exists)
    }

    pub async fn invalidate_pattern(&self, pattern: &str) -> Result<u64> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let prefixed_pattern = self.prefixed_key(pattern);
        let keys: Vec<String> = redis::cmd("KEYS")
            .arg(&prefixed_pattern)
            .query_async(&mut conn)
            .await?;
        let count = keys.len() as u64;

        if !keys.is_empty() {
            let _: () = conn.del(&keys).await?;
        }

        tracing::debug!("Invalidated {} keys matching pattern: {}", count, pattern);
        Ok(count)
    }

    pub async fn get_or_fetch<F, Fut, T>(&self, key: &str, ttl_secs: u64, fetcher: F) -> Result<T>
    where
        F: FnOnce() -> Fut,
        Fut: std::future::Future<Output = Result<T>>,
        T: DeserializeOwned + Serialize,
    {
        if let Some(cached) = self.get::<T>(key).await? {
            tracing::debug!("Redis cache hit for key: {}", key);
            return Ok(cached);
        }

        tracing::debug!("Redis cache miss for key: {}", key);
        let value = fetcher().await?;
        if let Err(e) = self.set(key, &value, ttl_secs).await {
            tracing::warn!("Failed to cache value in Redis: {}", e);
        }
        Ok(value)
    }

    pub async fn get_health(&self) -> Result<Option<String>> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = self.prefixed_key("health");
        let value: Option<String> = conn.get(&key).await?;
        Ok(value)
    }

    pub async fn set_health(&self, status: &str) -> Result<()> {
        let ttl = *cache_config::REDIS_CACHE_TTL_HEALTH;
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = self.prefixed_key("health");
        let _: String = conn.set_ex(&key, status, ttl).await?;
        Ok(())
    }

    pub async fn get_video_meta(&self, uuid: &str) -> Result<Option<serde_json::Value>> {
        self.get(uuid).await
    }

    pub async fn set_video_meta(&self, uuid: &str, value: &serde_json::Value) -> Result<()> {
        let ttl = *cache_config::REDIS_CACHE_TTL_VIDEO_META;
        self.set(uuid, value, ttl).await
    }

    pub async fn invalidate_video_meta(&self, uuid: &str) -> Result<bool> {
        self.delete(uuid).await
    }

    pub async fn invalidate_videos_list(&self) -> Result<u64> {
        self.invalidate_pattern("videos:*").await
    }
}

impl Clone for RedisCache {
    fn clone(&self) -> Self {
        Self {
            client: Arc::clone(&self.client),
        }
    }
}

impl Default for RedisCache {
    fn default() -> Self {
        Self::new().expect("Failed to create Redis cache")
    }
}

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

    #[test]
    fn test_prefixed_key() {
        let cache = RedisCache::new().unwrap();
        assert_eq!(cache.prefixed_key("test"), "momentry:cache:test");
        assert_eq!(cache.prefixed_key("video:abc"), "momentry:cache:video:abc");
    }
}
