use chrono::{Duration, Utc};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, instrument, warn};
use uuid::Uuid;

/// Représente un changement de statut typing pour un utilisateur
#[derive(Debug, Clone)]
pub struct TypingStatusChange {
    pub user_id: Uuid,
    pub conversation_id: Uuid,
    pub is_typing: bool,
}

/// Manager pour gérer les typing indicators
pub struct TypingIndicatorManager {
    /// Map de conversation ID vers map de user ID vers timestamp de dernière activité
    typing_users: Arc<RwLock<HashMap<Uuid, HashMap<Uuid, chrono::DateTime<Utc>>>>>,
    /// Durée après laquelle un user n'est plus considéré comme "en train de taper"
    timeout_duration: Duration,
}

impl TypingIndicatorManager {
    pub fn new() -> Self {
        Self {
            typing_users: Arc::new(RwLock::new(HashMap::new())),
            timeout_duration: Duration::seconds(3),
        }
    }

    /// Marquer qu'un user est en train de taper dans une conversation
    #[instrument(skip(self))]
    pub async fn user_started_typing(&self, user_id: Uuid, conversation_id: Uuid) {
        let mut typing = self.typing_users.write().await;

        let conversation_typing = typing.entry(conversation_id).or_insert_with(HashMap::new);

        conversation_typing.insert(user_id, Utc::now());

        info!(
            user_id = %user_id,
            conversation_id = %conversation_id,
            "User started typing"
        );
    }

    /// Retirer un user de la liste des users en train de taper
    #[instrument(skip(self))]
    pub async fn user_stopped_typing(&self, user_id: Uuid, conversation_id: Uuid) {
        let mut typing = self.typing_users.write().await;

        if let Some(conversation_typing) = typing.get_mut(&conversation_id) {
            conversation_typing.remove(&user_id);

            info!(
                user_id = %user_id,
                conversation_id = %conversation_id,
                "User stopped typing"
            );
        }
    }

    /// Obtenir la liste des users en train de taper dans une conversation
    pub async fn get_typing_users(&self, conversation_id: Uuid) -> Vec<Uuid> {
        let typing = self.typing_users.read().await;

        if let Some(conversation_typing) = typing.get(&conversation_id) {
            let now = Utc::now();
            let mut active_users = Vec::new();

            for (user_id, last_activity) in conversation_typing.iter() {
                let elapsed = now.signed_duration_since(*last_activity);

                if elapsed < self.timeout_duration {
                    active_users.push(*user_id);
                }
            }

            active_users
        } else {
            Vec::new()
        }
    }

    /// Détecter les utilisateurs dont le timeout a expiré et les retirer
    /// Retourne la liste des changements de statut (is_typing = false)
    #[instrument(skip(self))]
    pub async fn monitor_timeouts(&self) -> Vec<TypingStatusChange> {
        let mut typing = self.typing_users.write().await;
        let now = Utc::now();
        let mut expired_changes = Vec::new();

        for (conversation_id, conversation_typing) in typing.iter_mut() {
            let mut expired_users = Vec::new();

            for (user_id, last_activity) in conversation_typing.iter() {
                let elapsed = now.signed_duration_since(*last_activity);

                if elapsed >= self.timeout_duration {
                    expired_users.push(*user_id);
                }
            }

            // Retirer les utilisateurs expirés et créer les changements de statut
            for user_id in expired_users {
                conversation_typing.remove(&user_id);
                expired_changes.push(TypingStatusChange {
                    user_id,
                    conversation_id: *conversation_id,
                    is_typing: false,
                });

                debug!(
                    user_id = %user_id,
                    conversation_id = %conversation_id,
                    "User typing timeout expired"
                );
            }
        }

        // Retirer les conversations vides
        typing.retain(|_conversation_id, users| !users.is_empty());

        if !expired_changes.is_empty() {
            debug!(
                count = expired_changes.len(),
                "Detected expired typing indicators"
            );
        }

        expired_changes
    }

    /// Nettoyer les users expirés de manière périodique (méthode legacy, utiliser monitor_timeouts)
    #[deprecated(note = "Use monitor_timeouts() instead")]
    pub async fn cleanup_expired(&self) {
        let _ = self.monitor_timeouts().await;
    }
}

impl Default for TypingIndicatorManager {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[tokio::test]
    async fn test_typing_indicator_manager() {
        let manager = TypingIndicatorManager::new();

        let conv1 = Uuid::new_v4();
        let user1 = Uuid::new_v4();
        let user2 = Uuid::new_v4();

        // Test user_started_typing
        manager.user_started_typing(user1, conv1).await;
        manager.user_started_typing(user2, conv1).await;

        let typing_users = manager.get_typing_users(conv1).await;
        assert!(typing_users.contains(&user1));
        assert!(typing_users.contains(&user2));

        // Test user_stopped_typing
        manager.user_stopped_typing(user1, conv1).await;

        let typing_users = manager.get_typing_users(conv1).await;
        assert!(!typing_users.contains(&user1));
        assert!(typing_users.contains(&user2));

        // Test monitor_timeouts
        let expired = manager.monitor_timeouts().await;
        assert!(expired.is_empty()); // Pas encore expiré
    }
}
