//! Module de gestion des métadonnées audio
//!
//! Ce module permet d'extraire et gérer les métadonnées des fichiers audio
//! (titre, artiste, album, durée, etc.)

use anyhow::Result;
use lofty::config::ParseOptions;
use lofty::prelude::*;
use lofty::probe::Probe;
use serde::{Deserialize, Serialize};
use std::path::Path;

#[cfg(feature = "pmoserver")]
use utoipa::ToSchema;

/// Métadonnées d'une piste audio
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "pmoserver", derive(ToSchema))]
pub struct AudioMetadata {
    /// Titre de la piste
    #[cfg_attr(feature = "pmoserver", schema(example = "Wish You Were Here"))]
    pub title: Option<String>,

    /// Artiste de la piste
    #[cfg_attr(feature = "pmoserver", schema(example = "Pink Floyd"))]
    pub artist: Option<String>,

    /// Album de la piste
    #[cfg_attr(feature = "pmoserver", schema(example = "Wish You Were Here"))]
    pub album: Option<String>,

    /// Année de sortie
    #[cfg_attr(feature = "pmoserver", schema(example = 1975))]
    pub year: Option<u32>,

    /// Numéro de piste
    #[cfg_attr(feature = "pmoserver", schema(example = 1))]
    pub track_number: Option<u32>,

    /// Nombre total de pistes
    #[cfg_attr(feature = "pmoserver", schema(example = 5))]
    pub track_total: Option<u32>,

    /// Numéro de disque
    #[cfg_attr(feature = "pmoserver", schema(example = 1))]
    pub disc_number: Option<u32>,

    /// Nombre total de disques
    #[cfg_attr(feature = "pmoserver", schema(example = 1))]
    pub disc_total: Option<u32>,

    /// Genre musical
    #[cfg_attr(feature = "pmoserver", schema(example = "Progressive Rock"))]
    pub genre: Option<String>,

    /// Durée en secondes
    #[cfg_attr(feature = "pmoserver", schema(example = 334))]
    pub duration_secs: Option<u64>,

    /// Taux d'échantillonnage (Hz)
    #[cfg_attr(feature = "pmoserver", schema(example = 44100))]
    pub sample_rate: Option<u32>,

    /// Nombre de canaux
    #[cfg_attr(feature = "pmoserver", schema(example = 2))]
    pub channels: Option<u8>,

    /// Bitrate moyen (kbps)
    #[cfg_attr(feature = "pmoserver", schema(example = 1411))]
    pub bitrate: Option<u32>,

    /// Informations sur la conversion appliquée lors de l'ingestion
    #[cfg_attr(feature = "pmoserver", schema(example = json!({"mode":"transcode","source_codec":"mp3"})))]
    pub conversion: Option<AudioConversionInfo>,
}

/// Informations sur le processus de conversion
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "pmoserver", derive(ToSchema))]
pub struct AudioConversionInfo {
    /// Mode de conversion (ex: "passthrough", "transcode")
    #[cfg_attr(feature = "pmoserver", schema(example = "transcode"))]
    pub mode: String,

    /// Codec source détecté
    #[cfg_attr(feature = "pmoserver", schema(example = "mp3"))]
    pub source_codec: Option<String>,
}

impl AudioMetadata {
    /// Extrait les métadonnées depuis un fichier audio taggé
    ///
    /// Fonction interne commune pour extraire les métadonnées depuis un TaggedFile
    fn from_tagged_file(tagged_file: lofty::file::TaggedFile) -> Self {
        let properties = tagged_file.properties();
        let tag = tagged_file
            .primary_tag()
            .or_else(|| tagged_file.first_tag());

        let mut metadata = Self {
            title: None,
            artist: None,
            album: None,
            year: None,
            track_number: None,
            track_total: None,
            disc_number: None,
            disc_total: None,
            genre: None,
            duration_secs: Some(properties.duration().as_secs()),
            sample_rate: properties.sample_rate(),
            channels: properties.channels(),
            bitrate: properties.audio_bitrate(),
            conversion: None,
        };

        if let Some(tag) = tag {
            metadata.title = tag.title().map(|s| s.to_string());
            metadata.artist = tag.artist().map(|s| s.to_string());
            metadata.album = tag.album().map(|s| s.to_string());
            metadata.year = tag.year();
            metadata.track_number = tag.track();
            metadata.track_total = tag.track_total();
            metadata.disc_number = tag.disk();
            metadata.disc_total = tag.disk_total();
            metadata.genre = tag.genre().map(|s| s.to_string());
        }

        metadata
    }

    /// Extrait les métadonnées d'un fichier audio
    ///
    /// # Arguments
    ///
    /// * `path` - Chemin vers le fichier audio
    ///
    /// # Exemple
    ///
    /// ```rust,no_run
    /// use pmoaudiocache::metadata::AudioMetadata;
    /// use std::path::Path;
    ///
    /// let metadata = AudioMetadata::from_file(Path::new("track.flac")).unwrap();
    /// println!("Titre: {:?}", metadata.title);
    /// ```
    pub fn from_file(path: &Path) -> Result<Self> {
        let tagged_file = Probe::open(path)?.options(ParseOptions::new()).read()?;
        Ok(Self::from_tagged_file(tagged_file))
    }

    /// Crée des métadonnées depuis des données brutes audio
    ///
    /// # Arguments
    ///
    /// * `data` - Données audio brutes
    pub fn from_bytes(data: &[u8]) -> Result<Self> {
        let cursor = std::io::Cursor::new(data);
        let tagged_file = Probe::new(cursor)
            .guess_file_type()?
            .options(ParseOptions::new())
            .read()?;
        Ok(Self::from_tagged_file(tagged_file))
    }

    /// Génère une clé de collection basée sur l'artiste et l'album
    ///
    /// Retourne une clé au format "artist:album" si les deux sont disponibles,
    /// sinon retourne None
    pub fn collection_key(&self) -> Option<String> {
        match (&self.artist, &self.album) {
            (Some(artist), Some(album)) => {
                let normalized_artist = artist.to_lowercase().replace(" ", "_");
                let normalized_album = album.to_lowercase().replace(" ", "_");
                Some(format!("{}:{}", normalized_artist, normalized_album))
            }
            _ => None,
        }
    }

    /// Retourne la durée formatée pour DIDL-Lite (H:MM:SS)
    ///
    /// # Exemple
    ///
    /// ```
    /// use pmoaudiocache::AudioMetadata;
    ///
    /// let metadata = AudioMetadata {
    ///     duration_secs: Some(3665),
    ///     ..Default::default()
    /// };
    ///
    /// assert_eq!(metadata.duration_formatted(), Some("1:01:05".to_string()));
    /// ```
    pub fn duration_formatted(&self) -> Option<String> {
        self.duration_secs.map(|d| {
            let hours = d / 3600;
            let minutes = (d % 3600) / 60;
            let seconds = d % 60;
            format!("{}:{:02}:{:02}", hours, minutes, seconds)
        })
    }

    /// Convertit les métadonnées en Resource DIDL-Lite
    ///
    /// # Arguments
    ///
    /// * `url` - URL de la ressource audio
    ///
    /// # Exemple
    ///
    /// ```no_run
    /// use pmoaudiocache::AudioMetadata;
    ///
    /// let metadata = AudioMetadata {
    ///     duration_secs: Some(180),
    ///     sample_rate: Some(44100),
    ///     channels: Some(2),
    ///     ..Default::default()
    /// };
    ///
    /// let resource = metadata.to_didl_resource("http://localhost:8080/audio/tracks/abc123".into());
    /// assert_eq!(resource.url, "http://localhost:8080/audio/tracks/abc123");
    /// ```
    pub fn to_didl_resource(&self, url: String) -> pmodidl::Resource {
        pmodidl::Resource {
            // Aligne le protocolInfo sur ce que le renderer déclare dans Sink (audio/flac).
            // PN explicite pour compatibilité DLNA, sinon fallback générique.
            protocol_info: "http-get:*:audio/flac:DLNA.ORG_PN=FLAC".to_string(),
            bits_per_sample: None,
            sample_frequency: self.sample_rate.map(|sr| sr.to_string()),
            nr_audio_channels: self.channels.map(|ch| ch.to_string()),
            duration: self.duration_formatted(),
            url,
        }
    }
}

impl Default for AudioMetadata {
    fn default() -> Self {
        Self {
            title: None,
            artist: None,
            album: None,
            year: None,
            track_number: None,
            track_total: None,
            disc_number: None,
            disc_total: None,
            genre: None,
            duration_secs: None,
            sample_rate: None,
            channels: None,
            bitrate: None,
            conversion: None,
        }
    }
}

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

    #[test]
    fn test_collection_key() {
        let metadata = AudioMetadata {
            title: Some("Wish You Were Here".to_string()),
            artist: Some("Pink Floyd".to_string()),
            album: Some("Wish You Were Here".to_string()),
            year: Some(1975),
            track_number: Some(1),
            track_total: Some(5),
            disc_number: Some(1),
            disc_total: Some(1),
            genre: Some("Progressive Rock".to_string()),
            duration_secs: Some(334),
            sample_rate: Some(44100),
            channels: Some(2),
            bitrate: Some(1411),
            conversion: None,
        };

        assert_eq!(
            metadata.collection_key(),
            Some("pink_floyd:wish_you_were_here".to_string())
        );
    }

    #[test]
    fn test_collection_key_missing_album() {
        let metadata = AudioMetadata {
            title: Some("Test".to_string()),
            artist: Some("Artist".to_string()),
            album: None,
            year: None,
            track_number: None,
            track_total: None,
            disc_number: None,
            disc_total: None,
            genre: None,
            duration_secs: None,
            sample_rate: None,
            channels: None,
            bitrate: None,
            conversion: None,
        };

        assert_eq!(metadata.collection_key(), None);
    }
}
