use std::sync::{atomic::AtomicBool, Arc, Mutex};

use crate::errors::ControlPointError;
use crate::model::PlaybackState;
use crate::music_renderer::capabilities::{
    HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
    QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::musicrenderer::{
    build_didl_lite_metadata, parse_didl_duration, MusicRendererBackend,
};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::upnp_clients::{
    AvTransportClient, ConnectionInfo, ConnectionManagerClient, PositionInfo, ProtocolInfo,
    RenderingControlClient,
};
use crate::{DeviceIdentity, RendererInfo};

/// High-level handle representing a renderer and its optional AVTransport client.
#[derive(Clone, Debug)]
pub struct UpnpRenderer {
    avtransport: Option<AvTransportClient>,
    rendering_control: Option<RenderingControlClient>,
    connection_manager: Option<ConnectionManagerClient>,
    has_avtransport_set_next: bool,
    queue: Arc<Mutex<MusicQueue>>,
    /// Durée extraite du DIDL-Lite (fallback si l'ampli ne la retourne pas)
    cached_duration: Arc<Mutex<Option<String>>>,
    /// Flag indicating if currently playing a continuous stream (radio without duration)
    continuous_stream: Arc<Mutex<bool>>,
}

impl UpnpRenderer {
    /// Retourne true si le renderer à un service de type AVTransport.
    pub fn has_avtransport(&self) -> bool {
        self.avtransport.is_some()
    }

    /// Retourne true si le renderer à un service de type RenderingControl.
    pub fn has_rendering_control(&self) -> bool {
        self.rendering_control.is_some()
    }

    /// Retourne true si le renderer à un service de type ConnectionManager.
    pub fn has_connection_manager(&self) -> bool {
        self.connection_manager.is_some()
    }

    /// Retourne le client de type AVTransport contenant les URL de controle et d'abonnement.
    pub fn avtransport(&self) -> Result<&AvTransportClient, ControlPointError> {
        self.avtransport.as_ref().ok_or_else(|| {
            ControlPointError::upnp_operation_not_supported("AvTransport", "Renderer")
        })
    }

    /// Retourne le client de type RenderingControl contenant les URL de controle et d'abonnement.
    pub fn rendering_control(&self) -> Result<&RenderingControlClient, ControlPointError> {
        self.rendering_control.as_ref().ok_or_else(|| {
            ControlPointError::upnp_operation_not_supported("RenderingControl", "Renderer")
        })
    }

    /// Retourne le client de type ConnectionManager contenant les URL de controle et d'abonnement.
    pub fn connection_manager(&self) -> Result<&ConnectionManagerClient, ControlPointError> {
        self.connection_manager.as_ref().ok_or_else(|| {
            ControlPointError::upnp_operation_not_supported("ConnectionManager", "Renderer")
        })
    }

    pub fn protocol_info(&self) -> Result<ProtocolInfo, ControlPointError> {
        let cm = self.connection_manager()?;
        cm.get_protocol_info()
    }

    pub fn connection_ids(&self) -> Result<Vec<i32>, ControlPointError> {
        let cm = self.connection_manager()?;
        cm.get_current_connection_ids()
    }

    pub fn connection_info(&self, connection_id: i32) -> Result<ConnectionInfo, ControlPointError> {
        let cm = self.connection_manager()?;
        cm.get_current_connection_info(connection_id)
    }

    pub fn set_next_uri(&self, uri: &str, meta: &str) -> Result<(), ControlPointError> {
        if !self.has_avtransport_set_next {
            return Err(ControlPointError::upnp_operation_not_supported(
                "SetNextAVTransportURI",
                "Renderer",
            ));
        }
        let avt = self.avtransport()?;
        avt.set_next_av_transport_uri(uri, meta)
    }
}

impl UpnpRenderer {
    pub fn new(
        avtransport: Option<AvTransportClient>,
        rendering_control: Option<RenderingControlClient>,
        connection_manager: Option<ConnectionManagerClient>,
        has_avtransport_set_next: bool,
        queue: Arc<Mutex<MusicQueue>>,
    ) -> Self {
        Self {
            avtransport,
            rendering_control,
            connection_manager,
            has_avtransport_set_next,
            queue,
            cached_duration: Arc::new(Mutex::new(None)),
            continuous_stream: Arc::new(Mutex::new(false)),
        }
    }

    /// Returns true if currently playing a continuous stream (radio without duration)
    pub fn is_continuous_stream(&self) -> bool {
        *self.continuous_stream.lock().expect("continuous_stream mutex poisoned")
    }
}

impl RendererFromMediaRendererInfo for UpnpRenderer {
    fn from_renderer_info(info: &RendererInfo) -> Result<Self, ControlPointError> {
        // Prepare le service AVTTransport
        let avtransport = match (
            info.avtransport_control_url(),
            info.avtransport_service_type(),
        ) {
            (Some(url), Some(service)) => Some(AvTransportClient::new(url, service)),
            _ => None,
        };

        // Prepare le service RenderingControl
        let rendering_control = match (
            info.rendering_control_control_url(),
            info.rendering_control_service_type(),
        ) {
            (Some(url), Some(service)) => Some(RenderingControlClient::new(url, service)),
            _ => None,
        };

        // Prepare le service ConnectionManager
        let connection_manager = match (
            info.connection_manager_control_url(),
            info.connection_manager_service_type(),
        ) {
            (Some(url), Some(service)) => Some(ConnectionManagerClient::new(url, service)),
            _ => None,
        };

        // Exige AVTTransport et RenderingControl au minimum
        if avtransport.is_none() || rendering_control.is_none() {
            return Err(ControlPointError::UpnpError(format!(
                "Some mandatory services are missing on {:?}",
                info.id(),
            )));
        }

        // Create the internal queue
        let queue = Arc::new(Mutex::new(MusicQueue::from_renderer_info(info)?));

        Ok(Self {
            avtransport,
            rendering_control,
            connection_manager,
            has_avtransport_set_next: info.capabilities().has_avtransport_set_next(),
            queue,
            cached_duration: Arc::new(Mutex::new(None)),
            continuous_stream: Arc::new(Mutex::new(false)),
        })
    }

    fn to_backend(self) -> MusicRendererBackend {
        MusicRendererBackend::Upnp(self)
    }
}


impl QueueTransportControl for UpnpRenderer {
    fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
        let metadata = if let Some(ref track_metadata) = item.metadata {
            build_didl_lite_metadata(track_metadata, &item.uri, &item.protocol_info)
        } else {
            format!(
                r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="0" parentID="-1" restricted="1"><res protocolInfo="{}">{}</res></item></DIDL-Lite>"#,
                item.protocol_info, item.uri
            )
        };

        let duration = parse_didl_duration(&metadata);
        if let Some(ref dur) = duration {
            tracing::debug!("Caching duration from queue DIDL: {}", dur);
            *self.cached_duration.lock().expect("cached_duration mutex poisoned") = Some(dur.clone());
        } else {
            tracing::debug!("No duration to cache from queue DIDL");
            *self.cached_duration.lock().expect("cached_duration mutex poisoned") = None;
        }

        let avt = self.avtransport()?;
        avt.set_av_transport_uri(&item.uri, &metadata)?;
        avt.play(0, "1")?;

        Ok(())
    }

}

impl HasQueue for UpnpRenderer {
    fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
        &self.queue
    }
}

impl HasContinuousStream for UpnpRenderer {
    fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
        &self.continuous_stream
    }
}
/// Implémentation UPnP AV de `TransportControl` pour [`UpnpRenderer`].
///
/// Cette impl se base sur AVTransport (InstanceID = 0).
impl TransportControl for UpnpRenderer {
    fn play_uri(&self, uri: &str, meta: &str) -> Result<(), ControlPointError> {
        // Log du DIDL complet pour déboguer
        if !meta.is_empty() {
            tracing::debug!(
                "play_uri DIDL-Lite metadata: {}",
                &meta[..meta.len().min(500)]
            );
        }

        // Parse le DIDL pour extraire la durée (fallback pour certains amplis)
        let duration = parse_didl_duration(meta);
        if let Some(ref dur) = duration {
            tracing::debug!("Caching duration from DIDL: {}", dur);
            *self.cached_duration.lock().expect("cached_duration mutex poisoned") = Some(dur.clone());
        } else {
            tracing::debug!("No duration to cache from DIDL");
            *self.cached_duration.lock().expect("cached_duration mutex poisoned") = None;
        }

        let avt = self.avtransport()?;
        avt.set_av_transport_uri(uri, meta)?;
        avt.play(0, "1")
    }

    fn play(&self) -> Result<(), ControlPointError> {
        let avt = self.avtransport()?;
        avt.play(0, "1")
    }

    fn pause(&self) -> Result<(), ControlPointError> {
        let avt = self.avtransport()?;
        avt.pause(0)
    }

    fn stop(&self) -> Result<(), ControlPointError> {
        let avt = self.avtransport()?;
        avt.stop(0)
    }

    fn seek_rel_time(&self, hhmmss: &str) -> Result<(), ControlPointError> {
        let avt = self.avtransport()?;
        avt.seek(0, "REL_TIME", hhmmss)
    }
}

/// Implémentation UPnP RenderingControl de `VolumeControl` pour [`UpnpRenderer`].
///
/// Cette impl se base sur le channel "Master" (InstanceID = 0).
impl VolumeControl for UpnpRenderer {
    fn volume(&self) -> Result<u16, ControlPointError> {
        let rc = self.rendering_control()?;
        rc.get_volume(0, "Master")
    }

    fn set_volume(&self, v: u16) -> Result<(), ControlPointError> {
        let rc = self.rendering_control()?;
        rc.set_volume(0, "Master", v)
    }

    fn mute(&self) -> Result<bool, ControlPointError> {
        let rc = self.rendering_control()?;
        rc.get_mute(0, "Master")
    }

    fn set_mute(&self, m: bool) -> Result<(), ControlPointError> {
        let rc = self.rendering_control()?;
        rc.set_mute(0, "Master", m)
    }
}

/// Implémentation UPnP AV de `PlaybackStatus` pour [`UpnpRenderer`].
///
/// Utilise AVTransport::GetTransportInfo(InstanceID=0).
impl PlaybackStatus for UpnpRenderer {
    fn playback_state(&self) -> Result<PlaybackState, ControlPointError> {
        let avt = self.avtransport()?;
        let info = avt.get_transport_info(0)?;
        Ok(PlaybackState::from_upnp_state(
            &info.current_transport_state,
        ))
    }
}

impl PlaybackPosition for UpnpRenderer {
    fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
        let avt = self.avtransport()?;
        let raw: PositionInfo = avt.get_position_info(0)?;

        tracing::trace!(
            "UPnP GetPositionInfo returned: track_duration={:?}, rel_time={:?}",
            raw.track_duration,
            raw.rel_time
        );

        // Normalize "00:00:00" or "0:00:00" to None (some renderers return this for unknown duration)
        let track_duration = raw.track_duration.as_ref().and_then(|d| {
            if d == "00:00:00" || d == "0:00:00" { None } else { Some(d.clone()) }
        });

        let mut position = PlaybackPositionInfo {
            track: Some(raw.track),
            rel_time: raw.rel_time,
            abs_time: raw.abs_time,
            track_duration,
            track_metadata: None,
            track_uri: raw.track_uri,
        };

        // Replace device metadata with queue metadata (queue is authoritative and protected
        // against stale / decreasing durations for streams).
        crate::music_renderer::musicrenderer::enrich_position_from_queue(self, &mut position);

        tracing::trace!(
            "UPnP playback_position: track_duration={:?}, rel_time={:?}, using_queue_metadata={}",
            position.track_duration,
            position.rel_time,
            position.track_metadata.is_some()
        );

        Ok(position)
    }
}
