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, snapshot as snapshot_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
    }

    // --- Snapshot Cache Methods ---

    pub async fn snapshot_hits_key(file_uuid: &str) -> String {
        format!("snapshot:hits:{}", file_uuid)
    }

    pub async fn snapshot_status_key(file_uuid: &str) -> String {
        format!("snapshot:status:{}", file_uuid)
    }

    pub async fn snapshot_last_access_key(file_uuid: &str) -> String {
        format!("snapshot:last_access:{}", file_uuid)
    }

    pub async fn snapshot_migrate_hint_key(file_uuid: &str) -> String {
        format!("snapshot:migrate_hint:{}", file_uuid)
    }

    pub async fn increment_snapshot_hits(&self, file_uuid: &str) -> Result<u64> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_hits_key(file_uuid).await;
        let new_count: u64 = redis::cmd("INCR").arg(&key).query_async(&mut conn).await?;
        let _: () = redis::cmd("EXPIRE")
            .arg(&key)
            .arg(*snapshot_config::WARM_TTL_SECS)
            .query_async(&mut conn)
            .await?;
        Ok(new_count)
    }

    pub async fn get_snapshot_hits(&self, file_uuid: &str) -> Result<u64> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_hits_key(file_uuid).await;
        let count: Option<u64> = conn.get(&key).await?;
        Ok(count.unwrap_or(0))
    }

    pub async fn update_last_access(&self, file_uuid: &str) -> Result<()> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_last_access_key(file_uuid).await;
        let now: i64 = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;
        let _: String = conn
            .set_ex(&key, now, *snapshot_config::WARM_TTL_SECS)
            .await?;
        Ok(())
    }

    pub async fn set_snapshot_status(
        &self,
        file_uuid: &str,
        status: &str,
        progress: Option<f32>,
    ) -> Result<()> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_status_key(file_uuid).await;
        let payload = serde_json::json!({
            "status": status,
            "progress": progress,
        });
        let json = serde_json::to_string(&payload)?;
        let ttl = if status == "generating" {
            *crate::core::config::snapshot::GENERATING_TIMEOUT_SECS
        } else {
            *snapshot_config::WARM_TTL_SECS
        };
        let _: String = conn.set_ex(&key, json, ttl).await?;
        Ok(())
    }

    pub async fn get_snapshot_status(&self, file_uuid: &str) -> Result<serde_json::Value> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_status_key(file_uuid).await;
        let value: Option<String> = conn.get(&key).await?;
        match value {
            Some(json) => Ok(serde_json::from_str(&json)?),
            None => Ok(serde_json::json!({
                "status": "cold",
                "progress": null,
            })),
        }
    }

    pub async fn clear_snapshot_status(&self, file_uuid: &str) -> Result<()> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_status_key(file_uuid).await;
        let _: () = conn.del(&key).await?;
        Ok(())
    }

    pub async fn set_migrate_hint(
        &self,
        file_uuid: &str,
        parent_uuid: &str,
        count: u64,
    ) -> Result<()> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_migrate_hint_key(file_uuid).await;
        let payload = serde_json::json!({
            "parent_uuid": parent_uuid,
            "count": count,
        });
        let json = serde_json::to_string(&payload)?;
        let _: String = conn
            .set_ex(&key, json, *snapshot_config::WARM_TTL_SECS)
            .await?;
        Ok(())
    }

    pub async fn get_migrate_hint(&self, file_uuid: &str) -> Result<Option<serde_json::Value>> {
        let client = self.client.read().await;
        let mut conn = client.get_conn_internal().await?;
        let key = Self::snapshot_migrate_hint_key(file_uuid).await;
        let value: Option<String> = conn.get(&key).await?;
        match value {
            Some(json) => Ok(Some(serde_json::from_str(&json)?)),
            None => Ok(None),
        }
    }
}

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");
    }
}
