//! Système de broadcast de messages pour les rooms
//!
//! Ce module fournit un gestionnaire de broadcast utilisant `tokio::sync::broadcast`
//! pour diffuser efficacement des messages à tous les clients abonnés à une room.

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, error, info};
use uuid::Uuid;

use crate::websocket::OutgoingMessage;

/// Gestionnaire de broadcast pour les messages de chat
///
/// Utilise `tokio::sync::broadcast` pour diffuser des messages efficacement
/// à tous les clients abonnés à une room donnée.
pub struct BroadcastManager {
    /// Map des rooms avec leur canal de broadcast
    /// Chaque room a un `broadcast::Sender<String>` pour diffuser les messages JSON
    rooms: Arc<RwLock<HashMap<Uuid, broadcast::Sender<String>>>>,
    /// Capacité du canal de broadcast pour chaque room (nombre de messages en attente)
    channel_capacity: usize,
}

impl BroadcastManager {
    /// Crée un nouveau gestionnaire de broadcast
    ///
    /// # Arguments
    ///
    /// * `channel_capacity` - Capacité du canal de broadcast pour chaque room (défaut: 128)
    pub fn new(channel_capacity: Option<usize>) -> Self {
        Self {
            rooms: Arc::new(RwLock::new(HashMap::new())),
            channel_capacity: channel_capacity.unwrap_or(128),
        }
    }

    /// Crée ou récupère le canal de broadcast pour une room
    ///
    /// Retourne un receiver pour s'abonner aux messages de la room
    async fn get_or_create_room_channel(&self, room_id: Uuid) -> broadcast::Receiver<String> {
        let mut rooms = self.rooms.write().await;

        // Récupérer ou créer le canal pour cette room
        let sender = rooms
            .entry(room_id)
            .or_insert_with(|| {
                let (tx, _) = broadcast::channel(self.channel_capacity);
                info!("📡 Canal de broadcast créé pour la room {}", room_id);
                tx
            })
            .clone();

        // Créer un receiver pour cette room
        sender.subscribe()
    }

    /// S'abonne aux messages d'une room
    ///
    /// # Arguments
    ///
    /// * `room_id` - ID de la room à laquelle s'abonner
    ///
    /// # Returns
    ///
    /// Un `broadcast::Receiver<String>` pour recevoir les messages de la room
    pub async fn subscribe_to_room(&self, room_id: Uuid) -> broadcast::Receiver<String> {
        debug!("🔗 Subscription à la room {}", room_id);
        self.get_or_create_room_channel(room_id).await
    }

    /// Diffuse un message à tous les clients d'une room
    ///
    /// # Arguments
    ///
    /// * `room_id` - ID de la room dans laquelle diffuser
    /// * `message` - Message à diffuser
    ///
    /// # Returns
    ///
    /// Le nombre de clients qui ont reçu le message (ou 0 si la room n'existe pas)
    pub async fn broadcast_to_room(&self, room_id: Uuid, message: OutgoingMessage) -> usize {
        // Sérialiser le message en JSON
        let json = match serde_json::to_string(&message) {
            Ok(json) => json,
            Err(e) => {
                error!(
                    "❌ Erreur de sérialisation du message pour la room {}: {}",
                    room_id, e
                );
                return 0;
            }
        };

        let rooms = self.rooms.read().await;

        // Chercher le canal de broadcast pour cette room
        if let Some(sender) = rooms.get(&room_id) {
            match sender.send(json) {
                Ok(count) => {
                    debug!(
                        "📢 Message diffusé à {} clients dans la room {}",
                        count, room_id
                    );
                    count
                }
                Err(e) => {
                    // Aucun receiver actif (la room est vide)
                    debug!("⚠️ Aucun receiver actif pour la room {}: {}", room_id, e);
                    0
                }
            }
        } else {
            // La room n'existe pas encore (aucun subscriber)
            debug!(
                "⚠️ Tentative de broadcast dans une room inexistante: {}",
                room_id
            );
            0
        }
    }

    /// Retire une room du gestionnaire si elle n'a plus de subscribers
    ///
    /// Cette méthode nettoie les rooms vides pour libérer la mémoire.
    /// Appelée automatiquement lors des désinscriptions si nécessaire.
    ///
    /// # Arguments
    ///
    /// * `room_id` - ID de la room à nettoyer
    pub async fn cleanup_empty_room(&self, room_id: Uuid) {
        let mut rooms = self.rooms.write().await;

        if let Some(sender) = rooms.get(&room_id) {
            // Si le sender n'a plus de receivers actifs, on retire la room
            if sender.receiver_count() == 0 {
                rooms.remove(&room_id);
                info!("🧹 Room {} nettoyée (plus de subscribers)", room_id);
            }
        }
    }

    /// Obtient le nombre de subscribers actifs pour une room
    ///
    /// # Arguments
    ///
    /// * `room_id` - ID de la room
    ///
    /// # Returns
    ///
    /// Le nombre de subscribers actifs (ou 0 si la room n'existe pas)
    pub async fn subscriber_count(&self, room_id: Uuid) -> usize {
        let rooms = self.rooms.read().await;
        rooms
            .get(&room_id)
            .map(|sender| sender.receiver_count())
            .unwrap_or(0)
    }

    /// Obtient la liste de toutes les rooms actives
    ///
    /// # Returns
    ///
    /// Un vecteur d'IDs de rooms actives (avec au moins un subscriber)
    pub async fn active_rooms(&self) -> Vec<Uuid> {
        let rooms = self.rooms.read().await;
        rooms
            .iter()
            .filter(|(_, sender)| sender.receiver_count() > 0)
            .map(|(room_id, _)| *room_id)
            .collect()
    }

    /// Nettoie toutes les rooms vides
    ///
    /// Utile pour libérer la mémoire périodiquement
    pub async fn cleanup_all_empty_rooms(&self) {
        let mut rooms = self.rooms.write().await;
        let mut to_remove = Vec::new();

        for (room_id, sender) in rooms.iter() {
            if sender.receiver_count() == 0 {
                to_remove.push(*room_id);
            }
        }

        for room_id in to_remove {
            rooms.remove(&room_id);
            info!("🧹 Room {} nettoyée (plus de subscribers)", room_id);
        }
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::time::{sleep, Duration};

    #[tokio::test]
    async fn test_broadcast_manager_creation() {
        let manager = BroadcastManager::new(None);
        assert_eq!(manager.subscriber_count(Uuid::new_v4()).await, 0);
    }

    #[tokio::test]
    async fn test_subscribe_and_broadcast() {
        let manager = BroadcastManager::new(None);
        let room_id = Uuid::new_v4();

        // S'abonner à la room
        let mut receiver = manager.subscribe_to_room(room_id).await;
        assert_eq!(manager.subscriber_count(room_id).await, 1);

        // Créer un message de test
        let message = OutgoingMessage::ActionConfirmed {
            action: "test".to_string(),
            success: true,
        };

        // Diffuser le message
        let count = manager.broadcast_to_room(room_id, message).await;
        assert_eq!(count, 1);

        // Vérifier que le message a été reçu
        let received = tokio::time::timeout(Duration::from_millis(100), receiver.recv()).await;
        assert!(received.is_ok());
        if let Ok(Ok(json)) = received {
            assert!(!json.is_empty());
        }
    }

    #[tokio::test]
    async fn test_multiple_subscribers() {
        let manager = BroadcastManager::new(None);
        let room_id = Uuid::new_v4();

        // Créer plusieurs subscribers
        let mut receiver1 = manager.subscribe_to_room(room_id).await;
        let mut receiver2 = manager.subscribe_to_room(room_id).await;
        let mut receiver3 = manager.subscribe_to_room(room_id).await;

        assert_eq!(manager.subscriber_count(room_id).await, 3);

        // Diffuser un message
        let message = OutgoingMessage::ActionConfirmed {
            action: "broadcast".to_string(),
            success: true,
        };

        let count = manager.broadcast_to_room(room_id, message).await;
        assert_eq!(count, 3);

        // Vérifier que tous les receivers ont reçu le message
        let received1 = tokio::time::timeout(Duration::from_millis(100), receiver1.recv()).await;
        let received2 = tokio::time::timeout(Duration::from_millis(100), receiver2.recv()).await;
        let received3 = tokio::time::timeout(Duration::from_millis(100), receiver3.recv()).await;

        assert!(received1.is_ok());
        assert!(received2.is_ok());
        assert!(received3.is_ok());
    }

    #[tokio::test]
    async fn test_broadcast_to_nonexistent_room() {
        let manager = BroadcastManager::new(None);
        let room_id = Uuid::new_v4();

        let message = OutgoingMessage::ActionConfirmed {
            action: "test".to_string(),
            success: true,
        };

        // Diffuser dans une room sans subscribers
        let count = manager.broadcast_to_room(room_id, message).await;
        assert_eq!(count, 0);
    }

    #[tokio::test]
    async fn test_cleanup_empty_room() {
        let manager = BroadcastManager::new(None);
        let room_id = Uuid::new_v4();

        // S'abonner puis se désabonner
        let receiver = manager.subscribe_to_room(room_id).await;
        assert_eq!(manager.subscriber_count(room_id).await, 1);

        // Drop le receiver (simule une désinscription)
        drop(receiver);
        sleep(Duration::from_millis(10)).await; // Laisser le temps au cleanup

        // Nettoyer la room
        manager.cleanup_empty_room(room_id).await;
        assert_eq!(manager.subscriber_count(room_id).await, 0);
    }
}
