use super::media::MediaItem;
use super::state::PlayerState;
use crate::settings::AudioSettings;

/// Error type for player operations
#[derive(Debug, Clone)]
pub struct PlayerError {
    pub message: String,
}

impl std::fmt::Display for PlayerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for PlayerError {}

impl PlayerError {
    pub fn not_implemented() -> Self {
        Self {
            message: "Not implemented".to_string(),
        }
    }

    /// Create a playback failure error
    ///
    /// Only available on Android where ExoPlayer uses it for JNI errors
    #[cfg(target_os = "android")]
    pub fn playback_failed<S: Into<String>>(message: S) -> Self {
        Self {
            message: message.into(),
        }
    }
}

/// Player backend trait - implemented by platform-specific players
///
/// TRACES: UR-003, UR-004 | IR-003, IR-004 | DR-004
pub trait PlayerBackend: Send + Sync {
    /// Load a media item for playback
    /// TRACES: UR-005
    fn load(&mut self, media: &MediaItem) -> Result<(), PlayerError>;

    /// Start or resume playback
    /// TRACES: UR-005
    fn play(&mut self) -> Result<(), PlayerError>;

    /// Pause playback
    /// TRACES: UR-005
    fn pause(&mut self) -> Result<(), PlayerError>;

    /// Stop playback and unload media
    /// TRACES: UR-005
    fn stop(&mut self) -> Result<(), PlayerError>;

    /// Seek to a position in seconds
    /// TRACES: UR-005
    fn seek(&mut self, position: f64) -> Result<(), PlayerError>;

    /// Set volume (0.0 - 1.0)
    /// TRACES: UR-016
    fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError>;

    /// Get current playback position in seconds
    fn position(&self) -> f64;

    /// Get total duration in seconds
    fn duration(&self) -> Option<f64>;

    /// Get current player state
    fn state(&self) -> PlayerState;

    /// Get current volume
    fn volume(&self) -> f32;

    /// Apply audio settings (crossfade, gapless, normalization)
    ///
    /// @req-partial: UR-031 (Linux only) - Crossfade between audio tracks
    /// @req-partial: UR-032 (Linux only) - Gapless playback for seamless album listening
    /// @req-partial: UR-033 (Linux only) - Volume normalization to prevent volume jumps
    /// @req: DR-034 - Crossfade engine with configurable duration (0-12s)
    /// @req: DR-035 - Gapless playback between sequential tracks
    /// @req: DR-036 - Volume normalization with preset levels (Loud/Normal/Quiet)
    fn set_audio_settings(&mut self, _settings: &AudioSettings) -> Result<(), PlayerError> {
        // Default implementation does nothing - override in platform-specific backends
        Ok(())
    }

    /// Get current audio settings
    ///
    /// @req: DR-034 - Crossfade engine
    /// @req: DR-035 - Gapless playback
    /// @req: DR-036 - Volume normalization
    fn audio_settings(&self) -> AudioSettings {
        AudioSettings::default()
    }

    /// Set the active audio track by stream index
    ///
    /// Overridden by the Android (ExoPlayer) backend. `MpvBackend` deliberately
    /// does **not** override it — MPV is the audio-only backend here, so it keeps
    /// this `not_implemented()` default and the Linux video path switches track by
    /// re-opening the stream instead (`player_switch_audio_track`).
    ///
    /// TRACES: UR-021 | IR-019, DR-024
    fn set_audio_track(&mut self, _stream_index: i32) -> Result<(), PlayerError> {
        // Default implementation does nothing - override in platform-specific backends
        Err(PlayerError::not_implemented())
    }

    /// Set the active subtitle track by stream index (None to disable subtitles)
    ///
    /// Overridden by the Android (ExoPlayer) backend. `MpvBackend` deliberately
    /// does **not** override it, so it keeps this `not_implemented()` default;
    /// the Linux video path renders subtitles as `<track>` children of the
    /// WebKitGTK HTML5 `<video>` element and never calls this.
    ///
    /// TRACES: UR-020 | IR-018, DR-023
    fn set_subtitle_track(&mut self, _stream_index: Option<i32>) -> Result<(), PlayerError> {
        // Default implementation does nothing - override in platform-specific backends
        Err(PlayerError::not_implemented())
    }
}

/// Null player backend (for testing or when no real player is available)
///
/// @req: DR-004 - PlayerBackend trait (mock implementation for testing)
pub struct NullBackend {
    state: PlayerState,
    volume: f32,
    position: f64,
    duration: Option<f64>,
    audio_settings: AudioSettings,
}

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

impl NullBackend {
    pub fn new() -> Self {
        Self {
            state: PlayerState::Idle,
            volume: 1.0,
            position: 0.0,
            duration: None,
            audio_settings: AudioSettings::default(),
        }
    }
}

impl PlayerBackend for NullBackend {
    fn load(&mut self, media: &MediaItem) -> Result<(), PlayerError> {
        self.state = PlayerState::Loading {
            media: media.clone(),
        };
        // Simulate immediate load
        self.duration = media.duration;
        self.position = 0.0;
        self.state = PlayerState::Paused {
            media: media.clone(),
            position: 0.0,
            duration: media.duration.unwrap_or(0.0),
        };
        Ok(())
    }

    fn play(&mut self) -> Result<(), PlayerError> {
        if let PlayerState::Paused {
            media,
            position,
            duration,
        } = &self.state
        {
            self.state = PlayerState::Playing {
                media: media.clone(),
                position: *position,
                duration: *duration,
            };
        }
        Ok(())
    }

    fn pause(&mut self) -> Result<(), PlayerError> {
        if let PlayerState::Playing {
            media,
            position,
            duration,
        } = &self.state
        {
            self.state = PlayerState::Paused {
                media: media.clone(),
                position: *position,
                duration: *duration,
            };
        }
        Ok(())
    }

    fn stop(&mut self) -> Result<(), PlayerError> {
        self.state = PlayerState::Idle;
        self.position = 0.0;
        self.duration = None;
        Ok(())
    }

    fn seek(&mut self, position: f64) -> Result<(), PlayerError> {
        self.position = position;
        match &mut self.state {
            PlayerState::Playing { position: pos, .. } => *pos = position,
            PlayerState::Paused { position: pos, .. } => *pos = position,
            _ => {}
        }
        Ok(())
    }

    fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError> {
        self.volume = volume.clamp(0.0, 1.0);
        Ok(())
    }

    fn position(&self) -> f64 {
        self.position
    }

    fn duration(&self) -> Option<f64> {
        self.duration
    }

    fn state(&self) -> PlayerState {
        self.state.clone()
    }

    fn volume(&self) -> f32 {
        self.volume
    }

    fn set_audio_settings(&mut self, settings: &AudioSettings) -> Result<(), PlayerError> {
        self.audio_settings = settings.clone().with_crossfade_clamped();
        Ok(())
    }

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

// TRACES: UR-003, UR-004 | DR-004 | UT-026, UT-027, UT-028, UT-029, UT-030, UT-031, UT-032, UT-033
#[cfg(test)]
mod tests {
    use super::*;

    /// Test NullBackend volume default value
    /// TRACES: UR-016 | DR-004 | UT-026
    #[test]
    fn test_null_backend_volume_default() {
        let backend = NullBackend::new();
        assert_eq!(backend.volume(), 1.0);
    }

    /// Test NullBackend set volume
    ///
    /// @req-test: UT-027 - NullBackend set volume
    /// @req-test: UR-016 - Change system settings while playing (volume)
    #[test]
    fn test_null_backend_set_volume() {
        let mut backend = NullBackend::new();
        backend.set_volume(0.5).unwrap();
        assert_eq!(backend.volume(), 0.5);
    }

    /// Test NullBackend volume clamping (high)
    ///
    /// @req-test: UT-028 - NullBackend volume clamping (high/low)
    /// @req-test: UR-016 - Change system settings while playing (volume)
    #[test]
    fn test_null_backend_volume_clamping_high() {
        let mut backend = NullBackend::new();
        backend.set_volume(1.5).unwrap();
        assert_eq!(backend.volume(), 1.0);
    }

    /// Test NullBackend volume clamping (low)
    ///
    /// @req-test: UT-028 - NullBackend volume clamping (high/low)
    /// @req-test: UR-016 - Change system settings while playing (volume)
    #[test]
    fn test_null_backend_volume_clamping_low() {
        let mut backend = NullBackend::new();
        backend.set_volume(-0.5).unwrap();
        assert_eq!(backend.volume(), 0.0);
    }

    /// Test NullBackend volume boundary values
    ///
    /// @req-test: UT-029 - NullBackend volume boundary values
    /// @req-test: UR-016 - Change system settings while playing (volume)
    #[test]
    fn test_null_backend_volume_boundary() {
        let mut backend = NullBackend::new();

        backend.set_volume(0.0).unwrap();
        assert_eq!(backend.volume(), 0.0);

        backend.set_volume(1.0).unwrap();
        assert_eq!(backend.volume(), 1.0);
    }

    /// Test NullBackend audio settings default values
    ///
    /// @req-test: DR-034 - Crossfade engine
    /// @req-test: DR-035 - Gapless playback
    /// @req-test: DR-036 - Volume normalization
    #[test]
    fn test_null_backend_audio_settings_default() {
        let backend = NullBackend::new();
        let settings = backend.audio_settings();
        assert_eq!(settings.crossfade_duration, 0.0);
        assert!(settings.gapless_playback);
        assert!(!settings.normalize_volume);
    }

    /// Test NullBackend set audio settings
    ///
    /// @req-test: DR-034 - Crossfade engine with configurable duration
    /// @req-test: DR-035 - Gapless playback between sequential tracks
    /// @req-test: DR-036 - Volume normalization with preset levels
    #[test]
    fn test_null_backend_set_audio_settings() {
        use crate::settings::VolumeLevel;

        let mut backend = NullBackend::new();
        let settings = AudioSettings {
            crossfade_duration: 5.0,
            gapless_playback: false,
            normalize_volume: true,
            volume_level: VolumeLevel::Loud,
            ..Default::default()
        };

        backend.set_audio_settings(&settings).unwrap();

        let result = backend.audio_settings();
        assert_eq!(result.crossfade_duration, 5.0);
        assert!(!result.gapless_playback);
        assert!(result.normalize_volume);
        assert_eq!(result.volume_level, VolumeLevel::Loud);
    }

    /// Test NullBackend audio settings crossfade clamping to 12s max
    ///
    /// @req-test: DR-034 - Crossfade engine with configurable duration (0-12s)
    #[test]
    fn test_null_backend_audio_settings_crossfade_clamping() {
        let mut backend = NullBackend::new();
        let settings = AudioSettings {
            crossfade_duration: 20.0,
            ..Default::default()
        };

        backend.set_audio_settings(&settings).unwrap();
        assert_eq!(backend.audio_settings().crossfade_duration, 12.0);
    }

    /// Test NullBackend seek updates position
    ///
    /// @req-test: UR-005 - Control media playback (scrub operation)
    /// @req-test: DR-004 - PlayerBackend trait
    #[test]
    fn test_null_backend_seek_updates_position() {
        use crate::player::media::{MediaItem, MediaSource, MediaType};

        let mut backend = NullBackend::new();

        // Create a test media item
        let media = MediaItem {
            // Audio and direct-URL items never negotiate a transport.
            transport: None,
            id: "test_media".to_string(),
            title: "Test Track".to_string(),
            name: Some("Test Track".to_string()),
            artist: Some("Test Artist".to_string()),
            album: Some("Test Album".to_string()),
            album_name: Some("Test Album".to_string()),
            album_id: None,
            artist_items: None,
            artists: Some(vec!["Test Artist".to_string()]),
            primary_image_tag: None,
            image_id: None,
            item_type: Some("Audio".to_string()),
            playlist_id: None,
            duration: Some(180.0),
            artwork_url: None,
            media_type: MediaType::Audio,
            source: MediaSource::DirectUrl {
                url: "http://example.com/test.mp3".to_string(),
            },
            video_codec: None,
            needs_transcoding: false,
            video_width: None,
            video_height: None,
            subtitles: vec![],
            series_id: None,
            server_id: None,
        };

        // Load and play the media
        backend.load(&media).unwrap();
        backend.play().unwrap();

        // Verify initial position
        assert_eq!(backend.position(), 0.0);

        // Seek to 30 seconds
        backend.seek(30.0).unwrap();
        assert_eq!(backend.position(), 30.0);

        // Seek to 60 seconds
        backend.seek(60.0).unwrap();
        assert_eq!(backend.position(), 60.0);

        // Seek backward
        backend.seek(15.0).unwrap();
        assert_eq!(backend.position(), 15.0);
    }

    /// Test NullBackend seek while paused
    ///
    /// @req-test: UR-005 - Control media playback (scrub while paused)
    /// @req-test: DR-001 - Player state machine (seeking from paused state)
    #[test]
    fn test_null_backend_seek_while_paused() {
        use crate::player::media::{MediaItem, MediaSource, MediaType};

        let mut backend = NullBackend::new();

        let media = MediaItem {
            // Audio and direct-URL items never negotiate a transport.
            transport: None,
            id: "test_media".to_string(),
            title: "Test Track".to_string(),
            name: Some("Test Track".to_string()),
            artist: Some("Test Artist".to_string()),
            album: Some("Test Album".to_string()),
            album_name: Some("Test Album".to_string()),
            album_id: None,
            artist_items: None,
            artists: Some(vec!["Test Artist".to_string()]),
            primary_image_tag: None,
            image_id: None,
            item_type: Some("Audio".to_string()),
            playlist_id: None,
            duration: Some(180.0),
            artwork_url: None,
            media_type: MediaType::Audio,
            source: MediaSource::DirectUrl {
                url: "http://example.com/test.mp3".to_string(),
            },
            video_codec: None,
            needs_transcoding: false,
            video_width: None,
            video_height: None,
            subtitles: vec![],
            series_id: None,
            server_id: None,
        };

        // Load media (starts paused)
        backend.load(&media).unwrap();

        // Verify state is paused
        assert!(matches!(backend.state(), PlayerState::Paused { .. }));

        // Seek while paused
        backend.seek(45.0).unwrap();
        assert_eq!(backend.position(), 45.0);

        // Verify still paused
        assert!(matches!(backend.state(), PlayerState::Paused { .. }));
    }

    /// Test NullBackend position updates reflected in state
    ///
    /// @req-test: DR-001 - Player state machine (position tracking)
    /// @req-test: UR-005 - Control media playback (position accuracy)
    #[test]
    fn test_null_backend_position_updates_in_state() {
        use crate::player::media::{MediaItem, MediaSource, MediaType};

        let mut backend = NullBackend::new();

        let media = MediaItem {
            // Audio and direct-URL items never negotiate a transport.
            transport: None,
            id: "test_media".to_string(),
            title: "Test Track".to_string(),
            name: Some("Test Track".to_string()),
            artist: Some("Test Artist".to_string()),
            album: Some("Test Album".to_string()),
            album_name: Some("Test Album".to_string()),
            album_id: None,
            artist_items: None,
            artists: Some(vec!["Test Artist".to_string()]),
            primary_image_tag: None,
            image_id: None,
            item_type: Some("Audio".to_string()),
            playlist_id: None,
            duration: Some(180.0),
            artwork_url: None,
            media_type: MediaType::Audio,
            source: MediaSource::DirectUrl {
                url: "http://example.com/test.mp3".to_string(),
            },
            video_codec: None,
            needs_transcoding: false,
            video_width: None,
            video_height: None,
            subtitles: vec![],
            series_id: None,
            server_id: None,
        };

        backend.load(&media).unwrap();
        backend.play().unwrap();

        // Seek to 30 seconds
        backend.seek(30.0).unwrap();

        // Verify the state reflects the new position
        if let PlayerState::Playing { position, .. } = backend.state() {
            assert_eq!(position, 30.0);
        } else {
            panic!("Expected Playing state");
        }
    }
}
