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

use crate::music_renderer::capabilities::{
    HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
    QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::time_utils::{format_hhmmss_u32, parse_time_flexible};
use crate::DeviceIdentity;

use crate::errors::ControlPointError;
use crate::model::{PlaybackState, RendererInfo};
use crate::music_renderer::musicrenderer::MusicRendererBackend;
use crate::music_renderer::openhome::{
    build_info_client, build_playlist_client, build_product_client, build_radio_client,
    build_time_client, build_volume_client,
};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::upnp_clients::{
    OhInfoClient, OhPlaylistClient, OhProductClient, OhRadioClient, OhTimeClient, OhVolumeClient,
    OPENHOME_PLAYLIST_HEAD_ID,
};
use tracing::debug;

/// Cache for playback position to avoid redundant SOAP calls
#[derive(Debug)]
struct PositionCache {
    /// Last cached position info
    last_position: Option<PlaybackPositionInfo>,
    /// Timestamp of last cache update
    last_update: Option<SystemTime>,
    /// Number of calls in the last second (for warning detection)
    calls_in_last_second: Vec<SystemTime>,
}

impl PositionCache {
    fn new() -> Self {
        Self {
            last_position: None,
            last_update: None,
            calls_in_last_second: Vec::new(),
        }
    }
}

#[derive(Clone, Debug)]
pub struct OpenHomeRenderer {
    playlist: Option<OhPlaylistClient>,
    info_client: Option<OhInfoClient>,
    time_client: Option<OhTimeClient>,
    volume_client: Option<OhVolumeClient>,
    product_client: Option<OhProductClient>,
    #[allow(dead_code)]
    radio_client: Option<OhRadioClient>,
    queue: Arc<Mutex<MusicQueue>>,
    /// Flag indicating if currently playing a continuous stream (radio without duration)
    continuous_stream: Arc<Mutex<bool>>,
    /// Cached current track URI to detect track changes
    current_track_uri: Arc<Mutex<Option<String>>>,
    /// Position cache to avoid redundant SOAP calls (OpenHome has second-precision only)
    position_cache: Arc<Mutex<PositionCache>>,
}

impl OpenHomeRenderer {
    pub fn new(
        playlist: Option<OhPlaylistClient>,
        info_client: Option<OhInfoClient>,
        time_client: Option<OhTimeClient>,
        volume_client: Option<OhVolumeClient>,
        product_client: Option<OhProductClient>,
        radio_client: Option<OhRadioClient>,
        queue: Arc<Mutex<MusicQueue>>,
    ) -> Self {
        Self {
            playlist,
            info_client,
            time_client,
            volume_client,
            product_client,
            radio_client,
            queue,
            continuous_stream: Arc::new(Mutex::new(false)),
            current_track_uri: Arc::new(Mutex::new(None)),
            position_cache: Arc::new(Mutex::new(PositionCache::new())),
        }
    }

    /// 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")
    }

    pub fn has_playlist(&self) -> bool {
        self.playlist.is_some()
    }

    pub fn has_info(&self) -> bool {
        self.info_client.is_some()
    }

    pub fn has_time(&self) -> bool {
        self.time_client.is_some()
    }

    pub fn has_volume(&self) -> bool {
        self.volume_client.is_some()
    }

    pub fn has_any_openhome_service(&self) -> bool {
        self.has_playlist() || self.has_info() || self.has_time() || self.has_volume()
    }

    fn playlist_client_for(&self, op: &str) -> Result<&OhPlaylistClient, ControlPointError> {
        let playlist = self.playlist.as_ref().ok_or_else(|| {
            ControlPointError::upnp_operation_not_supported(op, "OpenHome Playlist")
        })?;
        self.ensure_playlist_source_selected()?;
        Ok(playlist)
    }

    fn info_client_for(&self, op: &str) -> Result<&OhInfoClient, ControlPointError> {
        self.info_client
            .as_ref()
            .ok_or_else(|| ControlPointError::upnp_operation_not_supported(op, "OpenHome Info"))
    }

    fn time_client_for(&self, op: &str) -> Result<&OhTimeClient, ControlPointError> {
        self.time_client
            .as_ref()
            .ok_or_else(|| ControlPointError::upnp_operation_not_supported(op, "OpenHome Time"))
    }

    fn volume_client_for(&self, op: &str) -> Result<&OhVolumeClient, ControlPointError> {
        self.volume_client
            .as_ref()
            .ok_or_else(|| ControlPointError::upnp_operation_not_supported(op, "OpenHome Volume"))
    }

    fn ensure_playlist_source_selected(&self) -> Result<(), ControlPointError> {
        if let Some(product) = &self.product_client {
            product.ensure_playlist_source_selected()
        } else {
            Ok(())
        }
    }

    // pub(crate) fn snapshot_openhome_playlist(&self) -> Result<OpenHomePlaylistSnapshot> {
    //     let playlist = self.playlist_client_for("snapshot_openhome_playlist")?;
    //     let entries = playlist.read_all_tracks()?;

    //     // Get current track ID from the playlist service
    //     let current_id = playlist.id().ok();

    //     let current_index =
    //         current_id.and_then(|id| entries.iter().position(|entry| entry.id == id));

    //     debug!(
    //         current_id = ?current_id,
    //         current_index = ?current_index,
    //         track_count = entries.len(),
    //         "snapshot_openhome_playlist completed"
    //     );

    //     let tracks = entries.iter().map(convert_oh_track_entry).collect();

    //     Ok(OpenHomePlaylistSnapshot {
    //         current_id,
    //         current_index,
    //         tracks,
    //     })
    // }

    /// Retourne la longueur de la playlist OpenHome sans récupérer toutes les métadonnées.
    /// Plus rapide que snapshot_openhome_playlist() pour juste connaître le nombre de pistes.
    pub(crate) fn openhome_playlist_len(&self) -> Result<usize, ControlPointError> {
        // Use queue.len() which uses cached track_ids() internally
        let queue = self.queue.lock().expect("queue mutex poisoned");
        queue.len()
    }

    /// Retourne les IDs des pistes de la playlist OpenHome.
    /// Plus rapide que snapshot_openhome_playlist() car ne récupère pas les métadonnées.
    pub(crate) fn openhome_playlist_ids(&self) -> Result<Vec<u32>, ControlPointError> {
        let queue = self.queue.lock().expect("queue mutex poisoned");
        if let Some(oh_queue) = queue.as_openhome() {
            oh_queue.track_ids()
        } else {
            Err(ControlPointError::QueueError(
                "Not an OpenHome queue".to_string(),
            ))
        }
    }

    pub(crate) fn clear_openhome_playlist(&self) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("clear_openhome_playlist")?;
        playlist.delete_all()
    }

    pub(crate) fn add_track_openhome(
        &self,
        uri: &str,
        metadata: &str,
        after_id: Option<u32>,
        play: bool,
    ) -> Result<u32, ControlPointError> {
        let playlist = self.playlist_client_for("add_track_openhome")?;
        let insert_after = match after_id {
            Some(id) => id,
            None => {
                let queue = self.queue.lock().expect("queue mutex poisoned");
                if let Some(oh_queue) = queue.as_openhome() {
                    oh_queue
                        .track_ids()?
                        .last()
                        .copied()
                        .unwrap_or(OPENHOME_PLAYLIST_HEAD_ID)
                } else {
                    return Err(ControlPointError::QueueError(
                        "Not an OpenHome queue".to_string(),
                    ));
                }
            }
        };

        let new_id = playlist.insert(insert_after, uri, metadata)?;
        if play {
            playlist.seek_id(new_id)?;
        }
        Ok(new_id)
    }

    pub(crate) fn play_openhome_track_id(&self, id: u32) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("play_openhome_track_id")?;
        playlist.seek_id(id)
    }
}

impl RendererFromMediaRendererInfo for OpenHomeRenderer {
    fn from_renderer_info(info: &RendererInfo) -> Result<Self, ControlPointError> {
        // Create the OpenHome queue
        let queue = Arc::new(Mutex::new(MusicQueue::from_renderer_info(info)?));

        let renderer = OpenHomeRenderer::new(
            build_playlist_client(&info),
            build_info_client(&info),
            build_time_client(&info),
            build_volume_client(&info),
            build_product_client(&info),
            build_radio_client(&info),
            queue,
        );

        if renderer.has_any_openhome_service() {
            Ok(renderer)
        } else {
            Err(ControlPointError::OpenHomeNotAValidDevice(format!(
                "{:?}",
                info.id()
            )))
        }
    }

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


impl TransportControl for OpenHomeRenderer {
    fn play_uri(&self, uri: &str, meta: &str) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("play_uri")?;

        if let Err(err) = playlist.delete_all() {
            debug!(
               // renderer = self.info.id.0.as_str(),
                error = %err,
                "Failed to clear OpenHome playlist before insert"
            );
        }

        // Reuse the same insertion logic as the queue path so that we honor
        // renderer expectations (IdArray sequencing, etc.).
        self.add_track_openhome(uri, meta, None, true)?;

        // Start playback (like UPnP renderer does with avt.play())
        playlist.play()?;
        Ok(())
    }

    fn play(&self) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("play")?;
        playlist.play()
    }

    fn pause(&self) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("pause")?;
        playlist.pause()
    }

    fn stop(&self) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("stop")?;
        playlist.stop()
    }

    fn seek_rel_time(&self, hhmmss: &str) -> Result<(), ControlPointError> {
        let seconds = parse_time_flexible(hhmmss)?;
        let playlist = self.playlist_client_for("seek_rel_time")?;
        playlist.seek_second_absolute(seconds)
    }
}

impl VolumeControl for OpenHomeRenderer {
    fn volume(&self) -> Result<u16, ControlPointError> {
        let client = self.volume_client_for("volume")?;
        client.volume()
    }

    fn set_volume(&self, v: u16) -> Result<(), ControlPointError> {
        let client = self.volume_client_for("set_volume")?;
        client.set_volume(v)
    }

    fn mute(&self) -> Result<bool, ControlPointError> {
        let client = self.volume_client_for("mute")?;
        client.mute()
    }

    fn set_mute(&self, m: bool) -> Result<(), ControlPointError> {
        let client = self.volume_client_for("set_mute")?;
        client.set_mute(m)
    }
}

impl PlaybackStatus for OpenHomeRenderer {
    fn playback_state(&self) -> Result<PlaybackState, ControlPointError> {
        let client = self.playlist_client_for("playback_state")?;
        let raw = client.transport_state().map_err(|err| {
            tracing::warn!(
                error = %err,
                "OpenHome transport_state() failed — state change detection disabled"
            );
            err
        })?;
        let mapped = if raw.is_empty() {
            tracing::trace!("OpenHome TransportState: empty (device initializing)");
            PlaybackState::Transitioning
        } else {
            let mapped = map_openhome_state(&raw);
            tracing::trace!(
                raw_state = raw.as_str(),
                mapped_state = ?mapped,
                "OpenHome TransportState"
            );
            mapped
        };
        Ok(mapped)
    }
}

impl PlaybackPosition for OpenHomeRenderer {
    fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
        let now = SystemTime::now();

        // Check cache first
        {
            let mut cache = self.position_cache.lock().expect("position_cache mutex poisoned");

            // Track calls for warning detection
            cache.calls_in_last_second.push(now);
            // Keep only calls from last second
            cache.calls_in_last_second.retain(|t| {
                now.duration_since(*t)
                    .map(|d| d.as_millis() < 1000)
                    .unwrap_or(false)
            });

            // Warn if called more than 3 times in last second
            if cache.calls_in_last_second.len() > 3 {
                tracing::warn!(
                    "OpenHome playback_position() called {} times in last second - possible inefficiency",
                    cache.calls_in_last_second.len()
                );
            }

            // Return cached value if it's less than 900ms old (OpenHome has second precision)
            if let (Some(last_pos), Some(last_update)) = (&cache.last_position, cache.last_update) {
                if let Ok(elapsed) = now.duration_since(last_update) {
                    if elapsed.as_millis() < 900 {
                        tracing::trace!(
                            "OpenHome playback_position: returning cached value (age={}ms)",
                            elapsed.as_millis()
                        );
                        return Ok(last_pos.clone());
                    }
                }
            }
        }

        // Cache miss or stale - fetch from backend
        let time_info = self.time_client_for("playback_position")?.position()?;

        let mut track_id = None;
        let mut track_uri = None;
        let mut track_metadata_xml = None;

        // Get track ID from queue (uses cached data)
        let queue_guard_for_id = self.queue.lock().expect("queue mutex poisoned");
        if let Some(oh_queue) = queue_guard_for_id.as_openhome() {
            match oh_queue.current_track() {
                Ok(id_opt) => track_id = id_opt,
                Err(err) => debug!(
                    error = %err,
                    "Failed to read OpenHome track id"
                ),
            }
        }
        drop(queue_guard_for_id);

        // Use queue API to get current item with cached metadata
        let mut queue_guard = self.queue.lock().expect("queue mutex poisoned");
        if let Ok(Some((current_item, _))) = queue_guard.peek_current() {
            // Use metadata from queue cache (updated via OpenHome events)
            track_uri = Some(current_item.uri.clone());

            // Build DIDL metadata XML from cached TrackMetadata
            if let Some(ref metadata) = current_item.metadata {
                track_metadata_xml = Some(
                    crate::music_renderer::musicrenderer::build_didl_lite_metadata(
                        metadata,
                        &current_item.uri,
                        &current_item.protocol_info,
                    ),
                );
            }

            // Check if the URI has changed to detect track changes
            let mut cached_uri = self.current_track_uri.lock().expect("current_track_uri mutex poisoned");
            let uri_changed = cached_uri.as_ref() != Some(&current_item.uri);

            if uri_changed {
                tracing::debug!(
                    "OpenHome track URI changed: {:?} -> {:?}",
                    cached_uri,
                    current_item.uri
                );

                // Détecte si la nouvelle URL est un flux continu
                let is_stream = crate::music_renderer::is_continuous_stream_url(&current_item.uri);
                *self.continuous_stream.lock().expect("continuous_stream mutex poisoned") = is_stream;
                tracing::debug!("OpenHome URI changed, continuous_stream={}", is_stream);

                *cached_uri = Some(current_item.uri.clone());
            }
        }
        drop(queue_guard);

        // Get duration from Time service - duration_secs=0 means stream (no duration)
        let track_duration = if time_info.duration_secs == 0 {
            None
        } else {
            Some(format_hhmmss_u32(time_info.duration_secs))
        };

        let rel_time = format_hhmmss_u32(time_info.elapsed_secs);

        tracing::trace!(
            "OpenHome playback_position: duration_secs={}, track_duration={:?}, elapsed_secs={}, rel_time={}",
            time_info.duration_secs,
            track_duration,
            time_info.elapsed_secs,
            rel_time
        );

        let position_info = PlaybackPositionInfo {
            track: track_id,
            rel_time: Some(rel_time),
            abs_time: None,
            track_duration,
            track_metadata: track_metadata_xml,
            track_uri,
        };

        // Update cache with fresh data
        {
            let mut cache = self.position_cache.lock().expect("position_cache mutex poisoned");
            cache.last_position = Some(position_info.clone());
            cache.last_update = Some(now);
        }

        Ok(position_info)
    }
}
pub(crate) fn map_openhome_state(raw: &str) -> PlaybackState {
    match raw.trim().to_ascii_uppercase().as_str() {
        "PLAYING" => PlaybackState::Playing,
        "PAUSED" | "PAUSED_PLAYBACK" => PlaybackState::Paused,
        "STOPPED" => PlaybackState::Stopped,
        "BUFFERING" | "TRANSITIONING" => PlaybackState::Transitioning,
        other => PlaybackState::Unknown(other.to_string()),
    }
}

impl QueueTransportControl for OpenHomeRenderer {
    fn play_item(&self, _item: &PlaybackItem) -> Result<(), ControlPointError> {
        let playlist = self.playlist_client_for("play_item")?;
        playlist.play()
    }

    fn play_from_queue(&self) -> Result<(), ControlPointError> {
        {
            let queue = self
                .queue
                .lock()
                .map_err(|_| ControlPointError::QueueError("Queue mutex poisoned".into()))?;

            if queue.current_index()?.is_none() {
                if queue.len()? > 0 {
                    drop(queue);
                    let mut queue = self.queue.lock().map_err(|_| {
                        ControlPointError::QueueError("Queue mutex poisoned".into())
                    })?;
                    queue.set_index(Some(0))?;
                } else {
                    return Err(ControlPointError::QueueError("Queue is empty".into()));
                }
            }
        }

        let playlist = self.playlist_client_for("play_from_queue")?;
        playlist.play()
    }

    fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> {
        // For OpenHome, we need to convert index to track_id
        let track_id = {
            let queue = self
                .queue
                .lock()
                .map_err(|_| ControlPointError::QueueError("Queue mutex poisoned".into()))?;
            queue.position_to_id(index)?
        };

        // Seek to the track by ID
        let playlist = self.playlist_client_for("play_from_index")?;
        playlist.seek_id(track_id)?;

        // Update local queue index
        {
            let mut queue = self
                .queue
                .lock()
                .map_err(|_| ControlPointError::QueueError("Queue mutex poisoned".into()))?;
            queue.set_index(Some(index))?;
        }

        // Start playback
        playlist.play()?;
        Ok(())
    }
}

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

impl HasContinuousStream for OpenHomeRenderer {
    fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
        &self.continuous_stream
    }
}

impl OpenHomeRenderer {
    pub fn replace_queue_with_background(
        &mut self,
        items: Vec<PlaybackItem>,
        current_index: Option<usize>,
    ) -> Result<(), ControlPointError> {
        self.queue
            .lock()
            .map_err(|_| ControlPointError::QueueError("Mutex poisoned".into()))?
            .replace_queue(items, current_index)?;

        let queue = self.queue.clone();
        std::thread::spawn(move || {
            debug!("🔄 OpenHome: préchargement métadonnées queue en background");
            if let Ok(mut queue) = queue.lock() {
                if let Ok(Some(idx)) = queue.current_index() {
                    let end = std::cmp::min(idx + 10, queue.len().unwrap_or(0));
                    for i in idx..end {
                        let _ = queue.get_item(i);
                        std::thread::sleep(std::time::Duration::from_millis(5));
                    }
                }
            }
            debug!("✅ OpenHome: préchargement métadonnées terminé");
        });

        Ok(())
    }
}
