//! # Source Registry - Gestionnaire de sources musicales
//!
//! Ce module fournit un registre centralisé pour gérer les différentes sources musicales
//! (MusicSource) qui peuvent être diffusées par le MediaServer.
//!
//! ## Fonctionnalités
//!
//! - **Enregistrement de sources** : Ajout de sources musicales au registre
//! - **Accès aux sources** : Récupération des sources enregistrées par ID
//! - **Navigation multi-sources** : Combine les sources dans une hiérarchie unique
//! - **Thread-safe** : Utilise Arc et RwLock pour un accès concurrent

use pmosource::MusicSource;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Registre des sources musicales
///
/// Ce registre maintient une liste de toutes les sources musicales enregistrées
/// et permet de les récupérer par leur ID unique.
///
/// # Thread Safety
///
/// Le registre utilise `Arc<RwLock<...>>` pour permettre un accès concurrent sécurisé.
/// Plusieurs lecteurs peuvent accéder simultanément aux sources, mais l'enregistrement
/// de nouvelles sources nécessite un verrou exclusif.
///
/// # Examples
///
/// ```ignore
/// use pmomediaserver::source_registry::SourceRegistry;
///
/// let registry = SourceRegistry::new();
///
/// // Enregistrer une source
/// let source = Arc::new(MyMusicSource::new());
/// registry.register(source).await;
///
/// // Récupérer une source
/// if let Some(source) = registry.get("my-source-id").await {
///     let root = source.root_container().await?;
/// }
/// ```
#[derive(Clone)]
pub struct SourceRegistry {
    sources: Arc<RwLock<HashMap<String, Arc<dyn MusicSource>>>>,
}

impl SourceRegistry {
    /// Crée un nouveau registre vide
    ///
    /// # Examples
    ///
    /// ```
    /// use pmomediaserver::source_registry::SourceRegistry;
    ///
    /// let registry = SourceRegistry::new();
    /// ```
    pub fn new() -> Self {
        Self {
            sources: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Enregistre une nouvelle source musicale
    ///
    /// La source est identifiée par son ID unique (retourné par `source.id()`).
    /// Si une source avec le même ID existe déjà, elle sera remplacée.
    ///
    /// # Arguments
    ///
    /// * `source` - La source musicale à enregistrer (doit implémenter `MusicSource`)
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let source = Arc::new(RadioParadise::new());
    /// registry.register(source).await;
    /// ```
    pub async fn register(&self, source: Arc<dyn MusicSource>) {
        let id = source.id().to_string();
        let mut sources = self.sources.write().await;

        tracing::info!(
            source_id = %id,
            source_name = %source.name(),
            "Registering music source"
        );

        sources.insert(id, source);
    }

    /// Récupère une source par son ID
    ///
    /// # Arguments
    ///
    /// * `id` - L'ID unique de la source
    ///
    /// # Returns
    ///
    /// Un `Arc` vers la source si elle existe, ou `None` si aucune source avec cet ID
    /// n'est enregistrée.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// if let Some(source) = registry.get("radio-paradise").await {
    ///     println!("Found: {}", source.name());
    /// }
    /// ```
    pub async fn get(&self, id: &str) -> Option<Arc<dyn MusicSource>> {
        let sources = self.sources.read().await;
        sources.get(id).cloned()
    }

    /// Liste toutes les sources enregistrées
    ///
    /// # Returns
    ///
    /// Un vecteur contenant des clones de toutes les sources enregistrées.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let all_sources = registry.list_all().await;
    /// for source in all_sources {
    ///     println!("Source: {} ({})", source.name(), source.id());
    /// }
    /// ```
    pub async fn list_all(&self) -> Vec<Arc<dyn MusicSource>> {
        let sources = self.sources.read().await;
        sources.values().cloned().collect()
    }

    /// Compte le nombre de sources enregistrées
    ///
    /// # Returns
    ///
    /// Le nombre total de sources dans le registre.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let count = registry.count().await;
    /// println!("Total sources: {}", count);
    /// ```
    pub async fn count(&self) -> usize {
        let sources = self.sources.read().await;
        sources.len()
    }

    /// Supprime une source du registre
    ///
    /// # Arguments
    ///
    /// * `id` - L'ID de la source à supprimer
    ///
    /// # Returns
    ///
    /// `true` si la source a été supprimée, `false` si elle n'existait pas.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// if registry.remove("old-source").await {
    ///     println!("Source removed");
    /// }
    /// ```
    pub async fn remove(&self, id: &str) -> bool {
        let mut sources = self.sources.write().await;

        if sources.remove(id).is_some() {
            tracing::info!(source_id = %id, "Removed music source");
            true
        } else {
            false
        }
    }

    /// Vérifie si une source est enregistrée
    ///
    /// # Arguments
    ///
    /// * `id` - L'ID de la source à vérifier
    ///
    /// # Returns
    ///
    /// `true` si la source existe, `false` sinon.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// if registry.contains("qobuz").await {
    ///     // La source Qobuz est disponible
    /// }
    /// ```
    pub async fn contains(&self, id: &str) -> bool {
        let sources = self.sources.read().await;
        sources.contains_key(id)
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use pmodidl::{Container, Item};
    use pmosource::{BrowseResult, MusicSource, Result};
    use std::time::SystemTime;

    #[derive(Debug)]
    struct TestSource {
        id: String,
        name: String,
    }

    impl TestSource {
        fn new(id: &str, name: &str) -> Self {
            Self {
                id: id.to_string(),
                name: name.to_string(),
            }
        }
    }

    #[async_trait::async_trait]
    impl MusicSource for TestSource {
        fn name(&self) -> &str {
            &self.name
        }

        fn id(&self) -> &str {
            &self.id
        }

        fn default_image(&self) -> &[u8] {
            &[]
        }

        async fn root_container(&self) -> Result<Container> {
            Ok(Container {
                id: self.id.clone(),
                parent_id: "0".to_string(),
                restricted: Some("1".to_string()),
                child_count: Some("0".to_string()),
                searchable: Some("1".to_string()),
                title: self.name.clone(),
                class: "object.container".to_string(),
                artist: None,
                album_art: None,
                containers: vec![],
                items: vec![],
            })
        }

        async fn browse(&self, _object_id: &str) -> Result<BrowseResult> {
            Ok(BrowseResult::Items(vec![]))
        }

        async fn resolve_uri(&self, object_id: &str) -> Result<String> {
            Ok(format!("http://example.com/{}", object_id))
        }

        fn supports_fifo(&self) -> bool {
            false
        }

        async fn append_track(&self, _track: Item) -> Result<()> {
            Err(pmosource::MusicSourceError::FifoNotSupported)
        }

        async fn remove_oldest(&self) -> Result<Option<Item>> {
            Err(pmosource::MusicSourceError::FifoNotSupported)
        }

        async fn update_id(&self) -> u32 {
            0
        }

        async fn last_change(&self) -> Option<SystemTime> {
            None
        }

        async fn get_items(&self, _offset: usize, _count: usize) -> Result<Vec<Item>> {
            Ok(vec![])
        }
    }

    #[tokio::test]
    async fn test_register_and_get() {
        let registry = SourceRegistry::new();
        let source = Arc::new(TestSource::new("test-1", "Test Source 1"));

        registry.register(source.clone()).await;

        let retrieved = registry.get("test-1").await;
        assert!(retrieved.is_some());

        let retrieved = retrieved.unwrap();
        assert_eq!(retrieved.id(), "test-1");
        assert_eq!(retrieved.name(), "Test Source 1");
    }

    #[tokio::test]
    async fn test_list_all() {
        let registry = SourceRegistry::new();

        registry
            .register(Arc::new(TestSource::new("test-1", "Test 1")))
            .await;
        registry
            .register(Arc::new(TestSource::new("test-2", "Test 2")))
            .await;
        registry
            .register(Arc::new(TestSource::new("test-3", "Test 3")))
            .await;

        let sources = registry.list_all().await;
        assert_eq!(sources.len(), 3);
    }

    #[tokio::test]
    async fn test_count() {
        let registry = SourceRegistry::new();
        assert_eq!(registry.count().await, 0);

        registry
            .register(Arc::new(TestSource::new("test-1", "Test 1")))
            .await;
        assert_eq!(registry.count().await, 1);

        registry
            .register(Arc::new(TestSource::new("test-2", "Test 2")))
            .await;
        assert_eq!(registry.count().await, 2);
    }

    #[tokio::test]
    async fn test_remove() {
        let registry = SourceRegistry::new();
        registry
            .register(Arc::new(TestSource::new("test-1", "Test 1")))
            .await;

        assert!(registry.contains("test-1").await);
        assert!(registry.remove("test-1").await);
        assert!(!registry.contains("test-1").await);
        assert!(!registry.remove("test-1").await);
    }

    #[tokio::test]
    async fn test_contains() {
        let registry = SourceRegistry::new();

        assert!(!registry.contains("test-1").await);

        registry
            .register(Arc::new(TestSource::new("test-1", "Test 1")))
            .await;

        assert!(registry.contains("test-1").await);
        assert!(!registry.contains("test-2").await);
    }

    #[tokio::test]
    async fn test_replace_source() {
        let registry = SourceRegistry::new();

        registry
            .register(Arc::new(TestSource::new("test-1", "Old Name")))
            .await;
        let old = registry.get("test-1").await.unwrap();
        assert_eq!(old.name(), "Old Name");

        registry
            .register(Arc::new(TestSource::new("test-1", "New Name")))
            .await;
        let new = registry.get("test-1").await.unwrap();
        assert_eq!(new.name(), "New Name");

        assert_eq!(registry.count().await, 1);
    }
}
