use super::backend::{PlayerBackend, PlayerError};
use super::events::{PlayerEventEmitter, PlayerStatusEvent};
use super::media::{MediaItem, MediaSource};
use super::state::PlayerState;
use crate::playback_reporting::{EventThrottler, PlaybackOperation, PlaybackReporter};
use crate::settings::AudioSettings;
use crate::utils::conversions::{seconds_to_ticks, volume_to_percent};
use crate::utils::lock::MutexSafe;
use libmpv::Mpv;
use log::{debug, error, info, warn};
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::Mutex as TokioMutex;

/// MPV-based player backend for Linux
///
/// Uses libmpv for audio playback with full control over playback state,
/// position tracking, and event handling.
pub struct MpvBackend {
    mpv: Arc<Mpv>,
    state: Arc<Mutex<InternalState>>,
    event_emitter: Option<Arc<dyn PlayerEventEmitter>>,
    audio_settings: AudioSettings,
    playback_reporter: Arc<TokioMutex<Option<PlaybackReporter>>>,
    position_throttler: Arc<EventThrottler>,
    last_seek_time: Arc<AtomicU64>,
}

struct InternalState {
    current_media: Option<MediaItem>,
    volume: f32,
}

/// Detect which audio system is available on the system
fn detect_audio_system() -> String {
    info!("[MpvBackend] Detecting audio system...");

    // Try PulseAudio/PipeWire first (most common on modern Linux)
    if let Ok(output) = Command::new("pactl").arg("info").output() {
        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            if stdout.contains("PipeWire") {
                info!("[MpvBackend] Detected PipeWire (with PulseAudio compatibility)");
                return "pulse".to_string();
            } else if stdout.contains("PulseAudio") {
                info!("[MpvBackend] Detected PulseAudio");
                return "pulse".to_string();
            }
        }
    }

    // Try detecting PipeWire directly
    if let Ok(output) = Command::new("pw-cli").arg("info").arg("0").output() {
        if output.status.success() {
            info!("[MpvBackend] Detected PipeWire");
            return "pulse".to_string(); // PipeWire works with pulse driver
        }
    }

    // Check if ALSA is available
    if std::path::Path::new("/proc/asound/cards").exists() {
        info!("[MpvBackend] Falling back to ALSA");
        return "alsa".to_string();
    }

    // Default fallback
    warn!("[MpvBackend] Could not detect audio system, using 'auto'");
    "auto".to_string()
}

/// Helper to get stream URL from MediaItem
fn get_stream_url(media: &MediaItem) -> String {
    match &media.source {
        MediaSource::Remote { stream_url, .. } => stream_url.clone(),
        MediaSource::Local { file_path, .. } => {
            format!("file://{}", file_path.to_string_lossy())
        }
        MediaSource::DirectUrl { url } => url.clone(),
    }
}

impl MpvBackend {
    /// Create a new MPV backend
    pub fn new(
        event_emitter: Option<Arc<dyn PlayerEventEmitter>>,
        playback_reporter: Arc<TokioMutex<Option<PlaybackReporter>>>,
        position_throttler: Arc<EventThrottler>,
    ) -> Result<Self, PlayerError> {
        info!("[MpvBackend] Initializing MPV backend...");

        // MPV requires LC_NUMERIC to be set to "C" locale
        // Set it before initializing MPV, then restore it after
        use std::ffi::CString;
        unsafe {
            let c_locale = CString::new("C").unwrap();
            libc::setlocale(libc::LC_NUMERIC, c_locale.as_ptr());
        }

        let mpv = Mpv::new().map_err(|e| PlayerError {
            message: format!("Failed to initialize MPV: {:?}", e),
        })?;

        // Detect and configure audio output
        let audio_driver = detect_audio_system();
        info!(
            "[MpvBackend] Configuring audio output driver: {}",
            audio_driver
        );

        mpv.set_property("ao", audio_driver.as_str())
            .map_err(|e| PlayerError {
                message: format!(
                    "Failed to set audio output to '{}': {:?}. Make sure audio system is working.",
                    audio_driver, e
                ),
            })?;

        // Enable verbose logging for audio initialization
        mpv.set_property("msg-level", "all=warn,ao=debug")
            .unwrap_or_else(|e| {
                warn!("[MpvBackend] Warning: Could not set MPV log level: {:?}", e);
            });

        // Configure MPV for audio playback
        mpv.set_property("audio-display", "no")
            .map_err(|e| PlayerError {
                message: format!("Failed to configure MPV audio-display: {:?}", e),
            })?;

        mpv.set_property("video", "no").map_err(|e| PlayerError {
            message: format!("Failed to configure MPV video: {:?}", e),
        })?;

        // Set volume to 100% (we'll control via MPV's volume property)
        mpv.set_property("volume", 100i64)
            .map_err(|e| PlayerError {
                message: format!("Failed to set initial volume: {:?}", e),
            })?;

        let state = Arc::new(Mutex::new(InternalState {
            current_media: None,
            volume: 1.0,
        }));

        let backend = MpvBackend {
            mpv: Arc::new(mpv),
            state,
            event_emitter,
            audio_settings: AudioSettings::default(),
            playback_reporter,
            position_throttler,
            last_seek_time: Arc::new(AtomicU64::new(0)),
        };

        // Start event loop in background thread
        backend.start_event_loop();

        info!("[MpvBackend] Initialized successfully");
        Ok(backend)
    }

    /// Start the MPV event loop in a background thread
    fn start_event_loop(&self) {
        let mpv = self.mpv.clone();
        let event_emitter = self.event_emitter.clone();
        let state = self.state.clone();
        let reporter = self.playback_reporter.clone();
        let throttler = self.position_throttler.clone();

        std::thread::spawn(move || {
            info!("[MpvBackend] Event loop started");

            let mut ev_ctx = mpv.create_event_context();
            ev_ctx.disable_deprecated_events().unwrap_or_else(|e| {
                error!("[MpvBackend] Failed to disable deprecated events: {:?}", e);
            });

            loop {
                match ev_ctx.wait_event(1.0) {
                    Some(Ok(event)) => match event {
                        libmpv::events::Event::StartFile => {
                            debug!("[MpvBackend] Starting file");
                        }
                        libmpv::events::Event::FileLoaded => {
                            info!("[MpvBackend] File loaded");

                            // Get duration
                            if let Ok(duration) = mpv.get_property::<f64>("duration") {
                                if let Some(emitter) = &event_emitter {
                                    emitter.emit(PlayerStatusEvent::MediaLoaded { duration });
                                }
                            }
                        }
                        libmpv::events::Event::PlaybackRestart => {
                            debug!("[MpvBackend] Playback started/resumed");

                            let media_id = state
                                .lock_safe()
                                .current_media
                                .as_ref()
                                .map(|m| m.id.clone());

                            if let Some(emitter) = &event_emitter {
                                emitter.emit(PlayerStatusEvent::StateChanged {
                                    state: "playing".to_string(),
                                    media_id,
                                });
                            }
                        }
                        libmpv::events::Event::PropertyChange { name, .. } if name == "pause" => {
                            // Handle pause state changes
                            if let Ok(is_paused) = mpv.get_property::<bool>("pause") {
                                let media_id = state
                                    .lock_safe()
                                    .current_media
                                    .as_ref()
                                    .map(|m| m.id.clone());

                                if let Some(emitter) = &event_emitter {
                                    emitter.emit(PlayerStatusEvent::StateChanged {
                                        state: if is_paused { "paused" } else { "playing" }
                                            .to_string(),
                                        media_id,
                                    });
                                }
                            }
                        }
                        libmpv::events::Event::EndFile(reason) => {
                            debug!("[MpvBackend] End file with reason: {}", reason);

                            // Only emit PlaybackEnded for natural track completion (EOF = 0)
                            // Don't emit for Stop (2), Quit (3), Error (4), or other reasons
                            // Constants from MPV_END_FILE_REASON enum: EOF=0, STOP=2, QUIT=3, ERROR=4
                            const MPV_END_FILE_REASON_EOF: u32 = 0;
                            const MPV_END_FILE_REASON_STOP: u32 = 2;
                            const MPV_END_FILE_REASON_QUIT: u32 = 3;
                            const MPV_END_FILE_REASON_ERROR: u32 = 4;

                            if reason == MPV_END_FILE_REASON_EOF {
                                debug!("[MpvBackend] Track finished naturally (EOF), emitting PlaybackEnded");
                                if let Some(emitter) = &event_emitter {
                                    emitter.emit(PlayerStatusEvent::PlaybackEnded);
                                }
                            } else if reason == MPV_END_FILE_REASON_STOP {
                                debug!("[MpvBackend] Track stopped (loading new track), NOT emitting PlaybackEnded");
                                // Don't emit - user is loading a new track
                            } else if reason == MPV_END_FILE_REASON_QUIT {
                                debug!("[MpvBackend] Player quitting, NOT emitting PlaybackEnded");
                                // Don't emit - player is shutting down
                            } else if reason == MPV_END_FILE_REASON_ERROR {
                                warn!("[MpvBackend] Track ended with error, NOT emitting PlaybackEnded");
                                // Don't emit - we should handle errors separately
                            } else {
                                debug!("[MpvBackend] Unknown end file reason {}, NOT emitting PlaybackEnded", reason);
                            }
                        }
                        libmpv::events::Event::Shutdown => {
                            info!("[MpvBackend] Shutdown event received");
                            break;
                        }
                        _ => {}
                    },
                    Some(Err(e)) => {
                        error!("[MpvBackend] Event error: {:?}", e);
                    }
                    None => {
                        // Timeout, continue
                    }
                }

                std::thread::sleep(Duration::from_millis(10));
            }

            info!("[MpvBackend] Event loop ended");
        });

        // Start position update thread
        let mpv_for_position = self.mpv.clone();
        let emitter_for_position = self.event_emitter.clone();
        let state_for_position = self.state.clone();
        let reporter_for_position = reporter.clone();
        let throttler_for_position = throttler.clone();
        let last_seek_time_for_position = self.last_seek_time.clone();

        std::thread::spawn(move || {
            loop {
                std::thread::sleep(Duration::from_millis(250));

                // Get current position and duration
                // Note: We emit position updates even when paused so scrubbing works
                if let (Ok(pos), Ok(dur)) = (
                    mpv_for_position.get_property::<f64>("time-pos"),
                    mpv_for_position.get_property::<f64>("duration"),
                ) {
                    // Check if we recently seeked - skip position updates briefly after seeks
                    // to avoid "jumping to zero" visual glitches while MPV is seeking
                    let now = SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .unwrap()
                        .as_millis() as u64;
                    let last_seek = last_seek_time_for_position.load(Ordering::Relaxed);
                    let time_since_seek = now.saturating_sub(last_seek);

                    // Skip position updates for 150ms after a seek to let MPV stabilize
                    if time_since_seek < 150 {
                        continue;
                    }

                    // Emit position update event (even when paused, for scrubbing)
                    if let Some(emitter) = &emitter_for_position {
                        emitter.emit(PlayerStatusEvent::PositionUpdate {
                            position: pos,
                            duration: dur,
                        });
                    }

                    // Check if we're playing for progress reporting
                    let is_paused = mpv_for_position
                        .get_property::<bool>("pause")
                        .unwrap_or(true);

                    // Only report progress to server when playing (not paused)
                    if !is_paused {
                        // Throttled progress reporting (every 30s)
                        let jellyfin_id = {
                            let state = state_for_position.lock_safe();
                            state
                                .current_media
                                .as_ref()
                                .and_then(|m| m.jellyfin_id().map(|s| s.to_string()))
                        };

                        if let Some(item_id) = jellyfin_id {
                            if throttler_for_position.should_report(&item_id) {
                                let position_ticks = seconds_to_ticks(pos);
                                let reporter_clone = reporter_for_position.clone();
                                let item_id_clone = item_id.clone();

                                // Spawn async task to report progress
                                // Check if we're in a Tokio runtime, otherwise spawn a new thread with its own runtime
                                if let Ok(handle) = tokio::runtime::Handle::try_current() {
                                    handle.spawn(async move {
                                        let reporter_guard = reporter_clone.lock().await;
                                        if let Some(reporter_instance) = reporter_guard.as_ref() {
                                            let operation = PlaybackOperation::Progress {
                                                item_id: item_id_clone.clone(),
                                                position_ticks,
                                                is_paused: false,
                                            };

                                            match reporter_instance.report(operation, true).await {
                                                Ok(_) => debug!(
                                                    "[MpvBackend] Reported progress for {}",
                                                    item_id_clone
                                                ),
                                                Err(e) => warn!(
                                                    "[MpvBackend] Failed to report progress: {}",
                                                    e
                                                ),
                                            }
                                        }
                                    });
                                } else {
                                    // Fallback: spawn in a new thread with its own runtime
                                    std::thread::spawn(move || {
                                        let rt = tokio::runtime::Runtime::new().unwrap();
                                        rt.block_on(async move {
                                            let reporter_guard = reporter_clone.lock().await;
                                            if let Some(reporter_instance) = reporter_guard.as_ref() {
                                                let operation = PlaybackOperation::Progress {
                                                    item_id: item_id_clone.clone(),
                                                    position_ticks,
                                                    is_paused: false,
                                                };

                                                match reporter_instance.report(operation, true).await {
                                                    Ok(_) => debug!("[MpvBackend] Reported progress for {}", item_id_clone),
                                                    Err(e) => warn!("[MpvBackend] Failed to report progress: {}", e),
                                                }
                                            }
                                        });
                                    });
                                }

                                throttler_for_position.mark_reported(&item_id);
                            }
                        }
                    }
                }
            }
        });
    }
}

impl PlayerBackend for MpvBackend {
    fn load(&mut self, media: &MediaItem) -> Result<(), PlayerError> {
        let stream_url = get_stream_url(media);
        info!("[MpvBackend] Loading: {} - {}", media.title, stream_url);

        // Update state
        {
            let mut state = self.state.lock_safe();
            state.current_media = Some(media.clone());
        }

        // Load the media file
        self.mpv
            .command("loadfile", &[&stream_url])
            .map_err(|e| PlayerError {
                message: format!("Failed to load file: {:?}", e),
            })?;

        debug!("[MpvBackend] Load command sent successfully");
        Ok(())
    }

    fn play(&mut self) -> Result<(), PlayerError> {
        debug!("[MpvBackend] Play command");

        self.mpv
            .set_property("pause", false)
            .map_err(|e| PlayerError {
                message: format!("Failed to play: {:?}", e),
            })?;

        Ok(())
    }

    fn pause(&mut self) -> Result<(), PlayerError> {
        debug!("[MpvBackend] Pause command");

        self.mpv
            .set_property("pause", true)
            .map_err(|e| PlayerError {
                message: format!("Failed to pause: {:?}", e),
            })?;

        Ok(())
    }

    fn stop(&mut self) -> Result<(), PlayerError> {
        debug!("[MpvBackend] Stop command");

        self.mpv.command("stop", &[]).map_err(|e| PlayerError {
            message: format!("Failed to stop: {:?}", e),
        })?;

        let mut state = self.state.lock_safe();
        state.current_media = None;

        Ok(())
    }

    fn seek(&mut self, position: f64) -> Result<(), PlayerError> {
        debug!("[MpvBackend] Seek to {} seconds", position);

        // Record the seek time to suppress position updates briefly
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;
        self.last_seek_time.store(now, Ordering::Relaxed);

        self.mpv
            .set_property("time-pos", position)
            .map_err(|e| PlayerError {
                message: format!("Failed to seek: {:?}", e),
            })?;

        Ok(())
    }

    fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError> {
        let clamped = volume.clamp(0.0, 1.0);
        debug!("[MpvBackend] Set volume to {}", clamped);

        // MPV expects volume as percentage (0-100)
        let mpv_volume = volume_to_percent(clamped as f64) as i64;

        self.mpv
            .set_property("volume", mpv_volume)
            .map_err(|e| PlayerError {
                message: format!("Failed to set volume: {:?}", e),
            })?;

        let mut state = self.state.lock_safe();
        state.volume = clamped;

        Ok(())
    }

    fn position(&self) -> f64 {
        self.mpv.get_property::<f64>("time-pos").unwrap_or(0.0)
    }

    fn duration(&self) -> Option<f64> {
        self.mpv
            .get_property::<f64>("duration")
            .ok()
            .filter(|d| *d > 0.0)
    }

    fn state(&self) -> PlayerState {
        let state = self.state.lock_safe();

        if let Some(ref media) = state.current_media {
            let is_paused = self.mpv.get_property::<bool>("pause").unwrap_or(true);
            let position = self.position();
            let duration = self.duration().unwrap_or(0.0);

            if is_paused {
                PlayerState::Paused {
                    media: media.clone(),
                    position,
                    duration,
                }
            } else {
                PlayerState::Playing {
                    media: media.clone(),
                    position,
                    duration,
                }
            }
        } else {
            PlayerState::Idle
        }
    }

    fn volume(&self) -> f32 {
        let state = self.state.lock_safe();
        state.volume
    }

    fn set_audio_settings(&mut self, settings: &AudioSettings) -> Result<(), PlayerError> {
        info!("[MpvBackend] Applying audio settings");
        self.audio_settings = settings.clone();

        // Apply gapless playback
        if settings.gapless_playback {
            self.mpv
                .set_property("gapless-audio", "yes")
                .map_err(|e| PlayerError {
                    message: format!("Failed to enable gapless: {:?}", e),
                })?;
        } else {
            self.mpv
                .set_property("gapless-audio", "no")
                .map_err(|e| PlayerError {
                    message: format!("Failed to disable gapless: {:?}", e),
                })?;
        }

        // TODO: Implement crossfade via MPV audio filters if needed
        // TODO: Implement volume normalization if needed

        Ok(())
    }

    fn audio_settings(&self) -> AudioSettings {
        self.audio_settings.clone()
    }
}

impl Drop for MpvBackend {
    fn drop(&mut self) {
        info!("[MpvBackend] Shutting down");
        // MPV will be automatically cleaned up
    }
}
