//! Playback-mode transfer commands (local ↔ remote).
//!
//! TRACES: UR-010 | DR-059

use std::sync::Arc;
use tauri::State;

use crate::playback_mode::{PlaybackMode, PlaybackModeManager};

/// Wrapper for PlaybackModeManager to manage in Tauri state
pub struct PlaybackModeManagerWrapper(pub Arc<PlaybackModeManager>);

/// Get the current playback mode
#[tauri::command]
#[specta::specta]
pub fn playback_mode_get_current(
    manager: State<'_, PlaybackModeManagerWrapper>,
) -> Result<PlaybackMode, String> {
    Ok(manager.0.get_mode())
}

/// Set the playback mode (internal/testing use)
#[tauri::command]
#[specta::specta]
pub fn playback_mode_set(
    manager: State<'_, PlaybackModeManagerWrapper>,
    mode: PlaybackMode,
) -> Result<(), String> {
    manager.0.set_mode(mode);
    Ok(())
}

/// Check if currently transferring between playback modes
#[tauri::command]
#[specta::specta]
pub fn playback_mode_is_transferring(
    manager: State<'_, PlaybackModeManagerWrapper>,
) -> Result<bool, String> {
    Ok(manager.0.is_transferring())
}

/// Transfer playback from local device to a remote Jellyfin session
#[tauri::command]
#[specta::specta]
pub async fn playback_mode_transfer_to_remote(
    manager: State<'_, PlaybackModeManagerWrapper>,
    session_id: String,
    position: Option<f64>,
) -> Result<(), String> {
    log::info!(
        "[PlaybackModeCommands] Transferring to remote session: {} (position override: {:?})",
        session_id,
        position
    );
    manager.0.transfer_to_remote(session_id, position).await
}

/// Set the transferring flag on the playback mode manager.
///
/// Used by the frontend remote->local flow to mark the whole two-step sequence
/// as a transfer, so `player_play_tracks` starts LOCAL playback instead of
/// casting back to the remote session it's leaving. Always pair `true` with a
/// later `false` (including on error) so the flag can't stick.
#[tauri::command]
#[specta::specta]
pub async fn playback_mode_set_transferring(
    manager: State<'_, PlaybackModeManagerWrapper>,
    transferring: bool,
) -> Result<(), String> {
    manager.0.set_transferring(transferring);
    Ok(())
}

/// Transfer playback from remote session back to local device
///
/// Parameters:
/// - current_item_id: The Jellyfin item ID currently playing on remote
/// - position_ticks: Current playback position in ticks (10,000 ticks = 1ms)
#[tauri::command]
#[specta::specta]
pub async fn playback_mode_transfer_to_local(
    manager: State<'_, PlaybackModeManagerWrapper>,
    current_item_id: String,
    position_ticks: i64,
) -> Result<(), String> {
    log::info!(
        "[PlaybackModeCommands] Transferring to local: item_id={}, position={}",
        current_item_id,
        position_ticks
    );
    manager
        .0
        .transfer_to_local(current_item_id, position_ticks)
        .await
}

/// Get remote session status (for polling position/duration)
#[tauri::command]
#[specta::specta]
pub async fn playback_mode_get_remote_status(
    manager: State<'_, PlaybackModeManagerWrapper>,
    player: State<'_, crate::commands::PlayerStateWrapper>,
) -> Result<RemoteSessionStatus, String> {
    let mode = manager.0.get_mode();

    if let crate::playback_mode::PlaybackMode::Remote { session_id } = mode {
        // Get Jellyfin client from player controller - clone before await
        let client = {
            let controller = player.0.lock().await;
            let client_arc = controller.jellyfin_client();
            let client_opt = client_arc.lock().map_err(|e| e.to_string())?;
            client_opt
                .as_ref()
                .ok_or("Jellyfin client not configured")?
                .clone()
        };

        // Get session info
        match client.get_session(&session_id).await {
            Ok(Some(session)) => {
                let position_ticks = session
                    .play_state
                    .as_ref()
                    .and_then(|ps| ps.position_ticks)
                    .unwrap_or(0);

                let duration_ticks = session
                    .now_playing_item
                    .as_ref()
                    .and_then(|item| item.run_time_ticks)
                    .unwrap_or(0);

                let is_paused = session
                    .play_state
                    .as_ref()
                    .and_then(|ps| ps.is_paused)
                    .unwrap_or(true);

                Ok(RemoteSessionStatus {
                    position: position_ticks as f64 / 10_000_000.0,
                    duration: if duration_ticks > 0 {
                        Some(duration_ticks as f64 / 10_000_000.0)
                    } else {
                        None
                    },
                    is_playing: !is_paused,
                    now_playing_item: session.now_playing_item.clone(),
                })
            }
            Ok(None) => Err("Remote session not found".to_string()),
            Err(e) => Err(format!("Failed to get session status: {}", e)),
        }
    } else {
        Err("Not in remote playback mode".to_string())
    }
}

/// Remote session status for UI updates
#[derive(specta::Type, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteSessionStatus {
    pub position: f64,
    pub duration: Option<f64>,
    pub is_playing: bool,
    pub now_playing_item: Option<crate::jellyfin::NowPlayingItem>,
}

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

    #[test]
    fn test_playback_mode_serialization() {
        // Test Local playback mode
        let local_mode = PlaybackMode::Local;
        let json = serde_json::to_string(&local_mode);
        assert!(json.is_ok());

        // Test Remote playback mode
        let remote_mode = PlaybackMode::Remote {
            session_id: "session-123".to_string(),
        };
        let json = serde_json::to_string(&remote_mode);
        assert!(json.is_ok());
    }

    #[test]
    fn test_remote_session_status_serialization() {
        let status = RemoteSessionStatus {
            position: 123.45,
            duration: Some(600.0),
            is_playing: true,
            now_playing_item: None,
        };

        // Should serialize successfully
        let json = serde_json::to_string(&status);
        assert!(json.is_ok());

        let serialized = json.unwrap();
        assert!(serialized.contains("123.45"));
        assert!(serialized.contains("600"));
        assert!(serialized.contains("true"));
    }

    #[test]
    fn test_remote_session_status_with_no_duration() {
        let status = RemoteSessionStatus {
            position: 0.0,
            duration: None,
            is_playing: false,
            now_playing_item: None,
        };

        let json = serde_json::to_string(&status).unwrap();
        assert!(json.contains("null") || json.contains("\"duration\":null"));
    }

    #[test]
    fn test_remote_session_status_various_positions() {
        let positions = vec![0.0, 30.5, 100.0, 3600.0];

        for pos in positions {
            let status = RemoteSessionStatus {
                position: pos,
                duration: Some(7200.0),
                is_playing: true,
                now_playing_item: None,
            };

            let json = serde_json::to_string(&status).unwrap();
            assert!(json.contains(&pos.to_string()));
        }
    }

    #[test]
    fn test_playback_mode_deserialization_from_frontend() {
        // Test what frontend sends for Idle mode
        let idle_json = r#"{"type":"idle"}"#;
        let mode: PlaybackMode =
            serde_json::from_str(idle_json).expect("Failed to deserialize idle");
        assert_eq!(mode, PlaybackMode::Idle);

        // Test what frontend sends for Local mode
        let local_json = r#"{"type":"local"}"#;
        let mode: PlaybackMode =
            serde_json::from_str(local_json).expect("Failed to deserialize local");
        assert_eq!(mode, PlaybackMode::Local);

        // Test what frontend sends for Remote mode
        let remote_json = r#"{"type":"remote","session_id":"session-123"}"#;
        let mode: PlaybackMode =
            serde_json::from_str(remote_json).expect("Failed to deserialize remote");
        match mode {
            PlaybackMode::Remote { session_id } => assert_eq!(session_id, "session-123"),
            _ => panic!("Expected Remote mode"),
        }
    }

    #[test]
    fn test_play_tracks_context_deserialization() {
        use crate::commands::PlayTracksContext;

        // Test Search context (the recently fixed issue)
        let search_json = r#"{"type":"search","searchQuery":"test query"}"#;
        let context: PlayTracksContext =
            serde_json::from_str(search_json).expect("Failed to deserialize search context");
        match context {
            PlayTracksContext::Search { search_query } => {
                assert_eq!(search_query, "test query");
            }
            _ => panic!("Expected Search context"),
        }

        // Test Playlist context
        let playlist_json =
            r#"{"type":"playlist","playlistId":"pl-123","playlistName":"My Playlist"}"#;
        let context: PlayTracksContext =
            serde_json::from_str(playlist_json).expect("Failed to deserialize playlist context");
        match context {
            PlayTracksContext::Playlist {
                playlist_id,
                playlist_name,
            } => {
                assert_eq!(playlist_id, "pl-123");
                assert_eq!(playlist_name, "My Playlist");
            }
            _ => panic!("Expected Playlist context"),
        }

        // Test Custom context
        let custom_json = r#"{"type":"custom","label":"Custom Queue"}"#;
        let context: PlayTracksContext =
            serde_json::from_str(custom_json).expect("Failed to deserialize custom context");
        match context {
            PlayTracksContext::Custom { label } => {
                assert_eq!(label, Some("Custom Queue".to_string()));
            }
            _ => panic!("Expected Custom context"),
        }
    }
}
