//! ChromecastSink - Diffuse le flux audio vers un périphérique Chromecast
//!
//! Ce module fournit un sink qui envoie le flux audio à un Chromecast.
//! Note: Cette implémentation est une version mock/skeleton. Une vraie implémentation
//! nécessiterait une bibliothèque comme `rust-cast` ou similaire.

use crate::{nodes::AudioError, AudioChunk};
use std::sync::Arc;
use tokio::sync::mpsc;

/// Configuration pour le ChromecastSink
#[derive(Debug, Clone)]
pub struct ChromecastConfig {
    /// Nom ou adresse IP du Chromecast
    pub device_address: String,

    /// Nom amical du device
    pub device_name: String,

    /// Port de communication (défaut: 8009)
    pub port: u16,

    /// Taille du buffer de streaming
    pub buffer_size: usize,

    /// Format d'encodage pour le streaming
    pub encoding: StreamEncoding,
}

impl Default for ChromecastConfig {
    fn default() -> Self {
        Self {
            device_address: "192.168.1.100".to_string(),
            device_name: "Living Room".to_string(),
            port: 8009,
            buffer_size: 50,
            encoding: StreamEncoding::Mp3,
        }
    }
}

/// Formats d'encodage supportés pour le streaming
#[derive(Debug, Clone, Copy)]
pub enum StreamEncoding {
    /// MP3 (compatible avec la plupart des Chromecasts)
    Mp3,
    /// AAC
    Aac,
    /// Opus
    Opus,
    /// PCM non compressé (haute qualité, bande passante élevée)
    Pcm,
}

/// ChromecastSink - Diffuse vers un périphérique Chromecast
///
/// Ce sink encode le flux audio et le streame vers un Chromecast.
/// La connexion est établie lors de l'initialisation et maintenue pendant toute la durée.
///
/// # Implémentation actuelle
///
/// Cette version est un mock qui simule l'envoi au Chromecast.
/// Pour une vraie implémentation, il faudrait:
/// - Utiliser une bibliothèque comme `rust-cast`
/// - Établir une connexion TLS avec le device
/// - Lancer une application de récepteur sur le Chromecast
/// - Encoder l'audio dans le format approprié
/// - Streamer via HTTP ou WebSocket
///
/// # Exemples
///
/// ```no_run
/// use pmoaudio::{ChromecastSink, ChromecastConfig};
///
/// #[tokio::main]
/// async fn main() {
///     let config = ChromecastConfig {
///         device_address: "192.168.1.100".to_string(),
///         device_name: "Living Room".to_string(),
///         ..Default::default()
///     };
///
///     let (sink, sink_tx) = ChromecastSink::new("chromecast1".to_string(), config, 10);
///
///     tokio::spawn(async move {
///         sink.run().await.unwrap()
///     });
/// }
/// ```
pub struct ChromecastSink {
    /// Identifiant du sink
    node_id: String,

    /// Channel pour recevoir les chunks audio
    rx: mpsc::Receiver<Arc<AudioChunk>>,

    /// Configuration
    config: ChromecastConfig,

    /// État de la connexion (mock)
    connected: bool,
}

impl ChromecastSink {
    /// Crée un nouveau ChromecastSink
    ///
    /// # Arguments
    ///
    /// * `node_id` - Identifiant unique du sink
    /// * `config` - Configuration du Chromecast
    /// * `channel_size` - Taille du buffer du channel
    pub fn new(
        node_id: String,
        config: ChromecastConfig,
        channel_size: usize,
    ) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
        let (tx, rx) = mpsc::channel(channel_size);

        let sink = Self {
            node_id,
            rx,
            config,
            connected: false,
        };

        (sink, tx)
    }

    /// Établit la connexion avec le Chromecast (mock)
    async fn connect(&mut self) -> Result<(), AudioError> {
        println!(
            "[{}] Connecting to Chromecast '{}' at {}:{}...",
            self.node_id, self.config.device_name, self.config.device_address, self.config.port
        );

        // Simuler une connexion
        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;

        self.connected = true;

        println!(
            "[{}] Connected to Chromecast '{}' successfully",
            self.node_id, self.config.device_name
        );

        Ok(())
    }

    /// Envoie un chunk au Chromecast (mock)
    async fn send_chunk(&self, _chunk: &AudioChunk) -> Result<(), AudioError> {
        if !self.connected {
            return Err(AudioError::ProcessingError(
                "Not connected to Chromecast".to_string(),
            ));
        }

        // Dans une vraie implémentation:
        // 1. Appliquer le gain
        // 2. Encoder dans le format approprié (MP3, AAC, etc.)
        // 3. Envoyer via le protocole Chromecast

        // Pour l'instant, simplement simuler un délai d'envoi
        tokio::time::sleep(tokio::time::Duration::from_micros(50)).await;

        Ok(())
    }

    /// Déconnecte proprement du Chromecast (mock)
    async fn disconnect(&mut self) -> Result<(), AudioError> {
        if self.connected {
            println!(
                "[{}] Disconnecting from Chromecast '{}'...",
                self.node_id, self.config.device_name
            );

            // Simuler la déconnexion
            tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;

            self.connected = false;

            println!("[{}] Disconnected successfully", self.node_id);
        }

        Ok(())
    }

    /// Démarre la boucle de traitement du ChromecastSink
    pub async fn run(mut self) -> Result<ChromecastStats, AudioError> {
        // Établir la connexion
        self.connect().await?;

        let mut stats = ChromecastStats::new(self.node_id.clone(), self.config.device_name.clone());

        // Boucle principale
        while let Some(chunk) = self.rx.recv().await {
            // Appliquer le gain si nécessaire
            let chunk_to_send = if chunk.gain_db().abs() > f64::EPSILON {
                Arc::clone(&chunk).apply_gain()
            } else {
                Arc::clone(&chunk)
            };

            // Envoyer au Chromecast
            self.send_chunk(&chunk_to_send).await?;

            stats.record_chunk(&chunk_to_send);
        }

        // Déconnexion propre
        self.disconnect().await?;

        stats.finalize();
        Ok(stats)
    }
}

/// Statistiques du ChromecastSink
#[derive(Debug, Clone)]
pub struct ChromecastStats {
    pub node_id: String,
    pub device_name: String,
    pub chunks_sent: u64,
    pub total_samples: u64,
    pub total_duration_sec: f64,
}

impl ChromecastStats {
    pub fn new(node_id: String, device_name: String) -> Self {
        Self {
            node_id,
            device_name,
            chunks_sent: 0,
            total_samples: 0,
            total_duration_sec: 0.0,
        }
    }

    pub fn record_chunk(&mut self, chunk: &AudioChunk) {
        self.chunks_sent += 1;
        self.total_samples += chunk.len() as u64;
        self.total_duration_sec += chunk.len() as f64 / chunk.sample_rate() as f64;
    }

    pub fn finalize(&mut self) {
        // Calculs finaux si nécessaire
    }

    pub fn display(&self) {
        println!("\n=== Chromecast Statistics: {} ===", self.node_id);
        println!("Device: {}", self.device_name);
        println!("Chunks sent: {}", self.chunks_sent);
        println!("Total samples: {}", self.total_samples);
        println!("Total duration: {:.3} sec", self.total_duration_sec);
        println!("==================================\n");
    }
}

#[cfg(test)]
mod tests {
    use std::i32;

    use super::*;
    use crate::BitDepth;

    #[tokio::test]
    async fn test_chromecast_sink_basic() {
        let config = ChromecastConfig {
            device_address: "127.0.0.1".to_string(),
            device_name: "Test Device".to_string(),
            ..Default::default()
        };

        let (sink, tx) = ChromecastSink::new("test".to_string(), config, 10);

        let handle = tokio::spawn(async move { sink.run().await });

        // Envoyer quelques chunks
        for i in 0..5 {
            let stereo = vec![[i32::MAX / 2; 2]; 1000];
            let chunk = AudioChunk::new(i, stereo, 48000, BitDepth::B24);
            tx.send(chunk).await.unwrap();
        }

        drop(tx);

        let stats = handle.await.unwrap().unwrap();
        assert_eq!(stats.chunks_sent, 5);
        assert_eq!(stats.device_name, "Test Device");
    }
}
