use anyhow::{Context, Result};
use futures_util::stream::StreamExt;
use redis::aio::MultiplexedConnection;
use redis::{AsyncCommands, Client};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::core::config::REDIS_KEY_PREFIX;

pub struct RedisClient {
    client: Client,
    state: Arc<RwLock<RedisState>>,
}

#[derive(Debug, Clone, Default)]
pub struct RedisState {
    pub connected: bool,
}

impl RedisClient {
    pub fn new() -> Result<Self> {
        let client = Client::open(crate::core::config::REDIS_URL.as_str())
            .context("Failed to connect to Redis")?;

        Ok(Self {
            client,
            state: Arc::new(RwLock::new(RedisState { connected: true })),
        })
    }

    pub async fn is_connected(&self) -> bool {
        self.state.read().await.connected
    }

    pub async fn get_conn(&self) -> Result<MultiplexedConnection> {
        self.get_conn_internal().await
    }

    pub async fn get_conn_internal(&self) -> Result<MultiplexedConnection> {
        self.client
            .get_multiplexed_async_connection()
            .await
            .context("Failed to get Redis connection")
    }

    pub async fn get_job_status(&self, uuid: &str) -> Result<Option<JobStatus>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}job:{}", prefix, uuid);

        let status: Option<String> = conn.hget(&key, "status").await?;
        if status.is_none() {
            return Ok(None);
        }

        let current_processor: String = conn.hget(&key, "current_processor").await?;
        let progress_total: i32 = conn.hget(&key, "progress_total").await?;
        let progress_current: i32 = conn.hget(&key, "progress_current").await?;
        let started_at: String = conn.hget(&key, "started_at").await?;
        let updated_at: String = conn.hget(&key, "updated_at").await?;
        let error_count: i32 = conn.hget(&key, "error_count").await?;
        let last_error: String = conn.hget(&key, "last_error").await?;

        Ok(Some(JobStatus {
            status: status.unwrap_or_default(),
            current_processor,
            progress_total,
            progress_current,
            started_at,
            updated_at,
            error_count,
            last_error,
        }))
    }

    pub async fn set_processor_status(
        &self,
        uuid: &str,
        processor: &str,
        status: &ProcessorStatus,
    ) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}job:{}:processor:{}", prefix, uuid, processor);

        let _: Option<String> = conn
            .hset_multiple(
                &key,
                &[
                    ("status", status.status.as_str()),
                    ("progress", status.progress.to_string().as_str()),
                    ("current", status.current.to_string().as_str()),
                    ("total", status.total.to_string().as_str()),
                    ("started_at", status.started_at.as_str()),
                    ("updated_at", status.updated_at.as_str()),
                    ("message", status.message.as_str()),
                ],
            )
            .await?;

        let _: bool = conn.expire(&key, 86400).await?;

        Ok(())
    }

    pub async fn get_processor_status(
        &self,
        uuid: &str,
        processor: &str,
    ) -> Result<Option<ProcessorStatus>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}job:{}:processor:{}", prefix, uuid, processor);

        let status: Option<String> = conn.hget(&key, "status").await?;
        if status.is_none() {
            return Ok(None);
        }

        let progress: i32 = conn.hget(&key, "progress").await?;
        let current: i32 = conn.hget(&key, "current").await?;
        let total: i32 = conn.hget(&key, "total").await?;
        let started_at: String = conn.hget(&key, "started_at").await?;
        let updated_at: String = conn.hget(&key, "updated_at").await?;
        let message: String = conn.hget(&key, "message").await?;

        Ok(Some(ProcessorStatus {
            status: status.unwrap_or_default(),
            progress,
            current,
            total,
            started_at,
            updated_at,
            message,
        }))
    }

    pub async fn publish_progress(&self, uuid: &str, message: &ProgressMessage) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let channel = format!("{}progress:{}", prefix, uuid);

        let json = serde_json::to_string(message)?;
        let _: usize = conn.publish(&channel, json).await?;

        Ok(())
    }

    pub async fn subscribe_progress(&self, uuid: &str) -> Result<redis::aio::PubSub> {
        let mut pubsub = self.client.get_async_pubsub().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let channel = format!("{}progress:{}", prefix, uuid);

        pubsub.subscribe(channel).await?;

        Ok(pubsub)
    }

    pub async fn subscribe_and_callback<F>(&self, uuid: &str, mut callback: F) -> Result<()>
    where
        F: FnMut(ProgressMessage) + Send + 'static,
    {
        let mut pubsub = self.subscribe_progress(uuid).await?;
        let mut stream = pubsub.on_message();

        while let Some(msg) = stream.next().await {
            let payload: String = msg.get_payload().map_err(|e| anyhow::anyhow!("{}", e))?;
            if let Ok(progress_msg) = serde_json::from_str::<ProgressMessage>(&payload) {
                callback(progress_msg);
            }
        }

        Ok(())
    }

    pub async fn add_to_active_jobs(&self, uuid: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let _: usize = conn.sadd(format!("{}jobs:active", prefix), uuid).await?;
        Ok(())
    }

    pub async fn move_to_completed_jobs(&self, uuid: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let _: bool = conn
            .smove(
                format!("{}jobs:active", prefix),
                format!("{}jobs:completed", prefix),
                uuid,
            )
            .await?;
        Ok(())
    }

    pub async fn move_to_failed_jobs(&self, uuid: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let _: bool = conn
            .smove(
                format!("{}jobs:active", prefix),
                format!("{}jobs:failed", prefix),
                uuid,
            )
            .await?;
        Ok(())
    }

    pub async fn get_active_jobs(&self) -> Result<Vec<String>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let jobs: Vec<String> = conn.smembers(format!("{}jobs:active", prefix)).await?;
        Ok(jobs)
    }

    pub async fn set_health(&self, status: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let _: String = conn
            .set_ex(format!("{}health:momentry_core", prefix), status, 60)
            .await?;
        Ok(())
    }

    pub async fn get_health(&self) -> Result<Option<String>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let health: Option<String> = conn.get(format!("{}health:momentry_core", prefix)).await?;
        Ok(health)
    }

    pub async fn sync_monitor_job(&self, job: &MonitorJobRedis) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}monitor:job:{}", prefix, job.uuid);

        let _: Option<String> = conn
            .hset_multiple(
                &key,
                &[
                    ("uuid", job.uuid.as_str()),
                    ("status", job.status.as_str()),
                    ("current_processor", job.current_processor.as_str()),
                    ("progress_total", job.progress_total.to_string().as_str()),
                    (
                        "progress_current",
                        job.progress_current.to_string().as_str(),
                    ),
                    ("error_count", job.error_count.to_string().as_str()),
                    ("started_at", job.started_at.as_str()),
                    ("updated_at", job.updated_at.as_str()),
                ],
            )
            .await?;

        let _: bool = conn.expire(&key, 86400).await?;

        Ok(())
    }

    pub async fn publish_job_error(&self, uuid: &str, error: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let channel = format!("{}errors:{}", prefix, uuid);

        let error_msg = JobErrorMessage {
            uuid: uuid.to_string(),
            error: error.to_string(),
            timestamp: chrono::Utc::now().timestamp(),
        };

        let json = serde_json::to_string(&error_msg)?;
        let _: usize = conn.publish(&channel, json).await?;

        Ok(())
    }

    pub async fn publish_anomaly_alert(&self, alert: &AnomalyAlertMessage) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let channel = format!("{}anomaly:alerts", prefix);

        let json = serde_json::to_string(alert)?;
        let _: usize = conn.publish(&channel, json).await?;

        let key = format!("{}anomaly:key:{}", prefix, alert.key_id);
        let alert_data = serde_json::json!({
            "key_id": alert.key_id,
            "anomaly_type": alert.anomaly_type,
            "severity": alert.severity,
            "timestamp": alert.timestamp,
            "message": alert.message,
        });
        let _: Option<String> = conn
            .hset(&key, "latest", &serde_json::to_string(&alert_data)?)
            .await?;
        let _: bool = conn.expire(&key, 86400).await?;

        Ok(())
    }

    pub async fn subscribe_anomaly_alerts(&self) -> Result<redis::aio::PubSub> {
        let mut pubsub = self.client.get_async_pubsub().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        pubsub
            .subscribe(format!("{}anomaly:alerts", prefix))
            .await?;
        Ok(pubsub)
    }

    pub async fn get_latest_anomaly(&self, key_id: &str) -> Result<Option<AnomalyAlertMessage>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}anomaly:key:{}", prefix, key_id);
        let latest: Option<String> = conn.hget(&key, "latest").await?;

        if let Some(json) = latest {
            let alert: AnomalyAlertMessage = serde_json::from_str(&json)?;
            Ok(Some(alert))
        } else {
            Ok(None)
        }
    }

    pub async fn subscribe_job_errors(&self, uuid: &str) -> Result<redis::aio::PubSub> {
        let mut pubsub = self.client.get_async_pubsub().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let channel = format!("{}errors:{}", prefix, uuid);

        pubsub.subscribe(channel).await?;

        Ok(pubsub)
    }

    pub async fn update_worker_job_status(
        &self,
        uuid: &str,
        job_id: i32,
        status: &str,
        current_processor: Option<&str>,
        progress: i32,
        total: i32,
    ) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}worker:job:{}", prefix, uuid);

        let _: Option<String> = conn
            .hset_multiple(
                &key,
                &[
                    ("job_id", job_id.to_string().as_str()),
                    ("status", status),
                    ("current_processor", current_processor.unwrap_or("")),
                    ("progress_current", progress.to_string().as_str()),
                    ("progress_total", total.to_string().as_str()),
                    ("updated_at", &chrono::Utc::now().to_rfc3339()),
                ],
            )
            .await?;

        let _: bool = conn.expire(&key, 86400).await?;

        Ok(())
    }

    pub async fn update_worker_processor_status(
        &self,
        uuid: &str,
        processor: &str,
        status: &str,
        error: Option<&str>,
    ) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}worker:job:{}:processor:{}", prefix, uuid, processor);

        let now = chrono::Utc::now().to_rfc3339();

        let mut fields: Vec<(&str, &str)> = vec![("status", status), ("updated_at", &now)];

        if let Some(err) = error {
            fields.push(("error", err));
        }

        let _: Option<String> = conn.hset_multiple(&key, &fields).await?;
        let _: bool = conn.expire(&key, 86400).await?;

        Ok(())
    }

    pub async fn get_worker_job_status(&self, uuid: &str) -> Result<Option<WorkerJobStatus>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let key = format!("{}worker:job:{}", prefix, uuid);

        let exists: bool = conn.exists(&key).await?;
        if !exists {
            return Ok(None);
        }

        let status: String = conn.hget(&key, "status").await?;
        let job_id: i32 = conn.hget(&key, "job_id").await?;
        let current_processor: String = conn.hget(&key, "current_processor").await?;
        let progress_current: i32 = conn.hget(&key, "progress_current").await?;
        let progress_total: i32 = conn.hget(&key, "progress_total").await?;
        let updated_at: String = conn.hget(&key, "updated_at").await?;

        Ok(Some(WorkerJobStatus {
            uuid: uuid.to_string(),
            job_id,
            status,
            current_processor,
            progress_current,
            progress_total,
            updated_at,
        }))
    }

    pub async fn delete_worker_job(&self, uuid: &str) -> Result<()> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();

        let key = format!("{}worker:job:{}", prefix, uuid);
        let _: i32 = conn.del(&key).await?;

        let processor_types = ["asr", "cut", "yolo", "ocr", "face", "pose", "asrx"];
        for ptype in processor_types {
            let proc_key = format!("{}worker:job:{}:processor:{}", prefix, uuid, ptype);
            let _: i32 = conn.del(&proc_key).await?;
        }

        Ok(())
    }

    pub async fn get_all_worker_jobs(&self) -> Result<Vec<WorkerJobInfo>> {
        let mut conn = self.get_conn_internal().await?;
        let prefix = REDIS_KEY_PREFIX.as_str();
        let keys: Vec<String> = conn.keys(format!("{}worker:job:*", prefix)).await?;

        let mut jobs = Vec::new();
        for key in keys {
            let uuid = key.replace(&format!("{}worker:job:", prefix), "");
            if let Some(status) = self.get_worker_job_status(&uuid).await? {
                jobs.push(WorkerJobInfo {
                    uuid,
                    job_id: status.job_id,
                    status: status.status,
                    progress_current: status.progress_current,
                    progress_total: status.progress_total,
                });
            }
        }

        Ok(jobs)
    }
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobStatus {
    pub status: String,
    pub current_processor: String,
    pub progress_total: i32,
    pub progress_current: i32,
    pub started_at: String,
    pub updated_at: String,
    pub error_count: i32,
    pub last_error: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessorStatus {
    pub status: String,
    pub progress: i32,
    pub current: i32,
    pub total: i32,
    pub started_at: String,
    pub updated_at: String,
    pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressMessage {
    #[serde(rename = "type")]
    pub msg_type: String,
    pub processor: String,
    pub uuid: String,
    pub timestamp: i64,
    pub data: ProgressData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressData {
    pub message: Option<String>,
    pub current: Option<i32>,
    pub total: Option<i32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorJobRedis {
    pub uuid: String,
    pub status: String,
    pub current_processor: String,
    pub progress_total: i32,
    pub progress_current: i32,
    pub error_count: i32,
    pub started_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobErrorMessage {
    pub uuid: String,
    pub error: String,
    pub timestamp: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyAlertMessage {
    pub key_id: String,
    pub anomaly_type: String,
    pub severity: String,
    pub ip_address: Option<String>,
    pub request_count: Option<i32>,
    pub error_rate: Option<f64>,
    pub unique_ips: Option<i32>,
    pub timestamp: i64,
    pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerJobStatus {
    pub uuid: String,
    pub job_id: i32,
    pub status: String,
    pub current_processor: String,
    pub progress_current: i32,
    pub progress_total: i32,
    pub updated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerJobInfo {
    pub uuid: String,
    pub job_id: i32,
    pub status: String,
    pub progress_current: i32,
    pub progress_total: i32,
}
