//! Queue manipulation commands (add / remove / move / skip).
//!
//! TRACES: UR-015 | DR-005, DR-020

use std::path::PathBuf;

use log::info;
use serde::Deserialize;
use tauri::State;

use super::{
    check_for_local_download, create_media_item, get_player_status, get_queue_status,
    DatabaseWrapper, PlayItemRequest, PlayerStateWrapper, PlayerStatus, QueueStatus,
};
use crate::commands::repository::RepositoryManagerWrapper;
use crate::player::{MediaItem, MediaSource, MediaType};
use crate::repository::types::{ImageOptions, ImageType};
use crate::repository::MediaRepository;

/// Request to add items to queue
#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddToQueueRequest {
    pub items: Vec<PlayItemRequest>,
    pub position: String, // "next" or "end"
}

/// Request to add a track by ID - backend fetches metadata
#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddTrackByIdRequest {
    pub track_id: String,
    pub position: String, // "next" or "end"
}

/// Request to add multiple tracks by IDs - backend fetches metadata
#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddTracksByIdsRequest {
    pub track_ids: Vec<String>,
    pub position: String, // "next" or "end"
}

#[tauri::command]
#[specta::specta]
pub async fn player_add_to_queue(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    request: AddToQueueRequest,
) -> Result<QueueStatus, String> {
    use crate::player::queue::AddPosition;

    let position = match request.position.as_str() {
        "next" => AddPosition::Next,
        _ => AddPosition::End,
    };

    // Create media items first (without holding any locks during await)
    let mut items: Vec<MediaItem> = Vec::new();
    for req in request.items {
        items.push(create_media_item(req, Some(&db)).await?);
    }

    // Now add to queue
    let controller = player.0.lock().await;
    let queue = controller.queue();
    let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;

    queue_lock.add(items, position);

    let result = QueueStatus {
        items: queue_lock.items().to_vec(),
        current_index: queue_lock.current_index(),
        shuffle: queue_lock.is_shuffle(),
        repeat: queue_lock.repeat_mode(),
        has_next: queue_lock.has_next(),
        has_previous: queue_lock.has_previous(),
    };

    drop(queue_lock);
    controller.emit_queue_changed();

    Ok(result)
}

#[tauri::command]
#[specta::specta]
pub async fn player_remove_from_queue(
    player: State<'_, PlayerStateWrapper>,
    index: usize,
) -> Result<QueueStatus, String> {
    let controller = player.0.lock().await;
    let queue = controller.queue();
    let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;

    queue_lock.remove(index);

    let result = QueueStatus {
        items: queue_lock.items().to_vec(),
        current_index: queue_lock.current_index(),
        shuffle: queue_lock.is_shuffle(),
        repeat: queue_lock.repeat_mode(),
        has_next: queue_lock.has_next(),
        has_previous: queue_lock.has_previous(),
    };

    drop(queue_lock);
    controller.emit_queue_changed();

    Ok(result)
}

#[tauri::command]
#[specta::specta]
pub async fn player_move_in_queue(
    player: State<'_, PlayerStateWrapper>,
    from_index: usize,
    to_index: usize,
) -> Result<QueueStatus, String> {
    let controller = player.0.lock().await;
    let queue = controller.queue();
    let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;

    if !queue_lock.move_item(from_index, to_index) {
        return Err("Invalid indices for move operation".to_string());
    }

    let result = QueueStatus {
        items: queue_lock.items().to_vec(),
        current_index: queue_lock.current_index(),
        shuffle: queue_lock.is_shuffle(),
        repeat: queue_lock.repeat_mode(),
        has_next: queue_lock.has_next(),
        has_previous: queue_lock.has_previous(),
    };

    drop(queue_lock);
    controller.emit_queue_changed();

    Ok(result)
}

/// Add a track to queue by ID - backend fetches metadata and constructs URLs
#[tauri::command]
#[specta::specta]
pub async fn player_add_track_by_id(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    repository_manager: State<'_, RepositoryManagerWrapper>,
    repository_handle: String,
    request: AddTrackByIdRequest,
) -> Result<QueueStatus, String> {
    use crate::player::queue::AddPosition;

    info!(
        "player_add_track_by_id called: track_id={}, position={}",
        request.track_id, request.position
    );

    // Get repository (hybrid - supports offline/online)
    let repository = repository_manager
        .0
        .get(&repository_handle)
        .ok_or("Repository not found - user may need to log in")?;

    // Fetch track metadata via repository
    info!(
        "Fetching metadata for track {} via repository",
        request.track_id
    );
    let track = repository
        .get_item(&request.track_id)
        .await
        .map_err(|e| format!("Failed to fetch track metadata: {}", e))?;

    // Check for local download first
    let local_path = check_for_local_download(&db, &request.track_id).await?;

    let source = if let Some(path) = local_path {
        info!("Using local download for track {}", request.track_id);
        MediaSource::Local {
            file_path: PathBuf::from(path),
            jellyfin_item_id: Some(track.id.clone()),
        }
    } else {
        // Get stream URL from repository (works online/offline)
        let stream_url = repository
            .get_audio_stream_url(&track.id)
            .await
            .map_err(|e| format!("Failed to get stream URL for {}: {}", track.name, e))?;

        MediaSource::Remote {
            stream_url,
            jellyfin_item_id: track.id.clone(),
        }
    };

    // Build MediaItem with artwork URL from repository and frontend-compatible fields
    let primary_image_tag_for_url = track.primary_image_tag.clone();
    let media_item = MediaItem {
        id: track.id.clone(),
        title: track.name.clone(),
        name: Some(track.name.clone()), // Frontend compatibility
        artist: track
            .album_artist
            .clone()
            .or_else(|| track.artists.as_ref().and_then(|a| a.first().cloned())),
        album: track.album_name.clone(),
        album_name: track.album_name.clone(), // Frontend compatibility
        album_id: track.album_id.clone(),
        artist_items: track.artist_items.clone(), // For clickable artist links
        artists: track.artists.clone(),           // Fallback artist info
        primary_image_tag: track.primary_image_tag.clone(), // For frontend image display
        image_id: track.primary_image_tag.clone(),
        item_type: Some(track.item_type.clone()), // Frontend compatibility
        playlist_id: None,
        duration: track.runtime_ticks.map(|t| t as f64 / 10_000_000.0),
        artwork_url: primary_image_tag_for_url.and_then(|tag| {
            track.album_id.as_ref().map(|album_id| {
                repository.get_image_url(
                    album_id,
                    ImageType::Primary,
                    Some(ImageOptions {
                        max_width: Some(300),
                        tag: Some(tag),
                        ..Default::default()
                    }),
                )
            })
        }),
        media_type: MediaType::Audio,
        source,
        video_codec: None,
        needs_transcoding: false,
        video_width: None,
        video_height: None,
        subtitles: vec![],
        series_id: None,
        server_id: None,
    };

    // Add to queue at specified position
    let position = match request.position.as_str() {
        "next" => AddPosition::Next,
        _ => AddPosition::End,
    };

    let controller = player.0.lock().await;
    let queue = controller.queue();
    let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;

    queue_lock.add(vec![media_item], position);

    let result = get_queue_status(&controller);
    drop(queue_lock);
    controller.emit_queue_changed();

    info!("Successfully added track {} to queue", request.track_id);
    Ok(result)
}

/// Add multiple tracks to queue by IDs - backend fetches metadata and constructs URLs
#[tauri::command]
#[specta::specta]
pub async fn player_add_tracks_by_ids(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    repository_manager: State<'_, RepositoryManagerWrapper>,
    repository_handle: String,
    request: AddTracksByIdsRequest,
) -> Result<QueueStatus, String> {
    use crate::player::queue::AddPosition;

    info!(
        "player_add_tracks_by_ids called: {} tracks, position={}",
        request.track_ids.len(),
        request.position
    );

    // Get repository (hybrid - supports offline/online)
    let repository = repository_manager
        .0
        .get(&repository_handle)
        .ok_or("Repository not found - user may need to log in")?;

    // Fetch metadata and build MediaItems for all tracks
    let mut media_items = Vec::new();
    for track_id in &request.track_ids {
        info!("Fetching metadata for track {} via repository", track_id);
        let track = repository
            .get_item(track_id)
            .await
            .map_err(|e| format!("Failed to fetch track metadata for {}: {}", track_id, e))?;

        // Check for local download first
        let local_path = check_for_local_download(&db, track_id).await?;

        let source = if let Some(path) = local_path {
            info!("Using local download for track {}", track_id);
            MediaSource::Local {
                file_path: PathBuf::from(path),
                jellyfin_item_id: Some(track.id.clone()),
            }
        } else {
            // Get stream URL from repository (works online/offline)
            let stream_url = repository
                .get_audio_stream_url(&track.id)
                .await
                .map_err(|e| format!("Failed to get stream URL for {}: {}", track.name, e))?;

            MediaSource::Remote {
                stream_url,
                jellyfin_item_id: track.id.clone(),
            }
        };

        // Build MediaItem with artwork URL from repository and frontend-compatible fields
        let primary_image_tag_for_url = track.primary_image_tag.clone();
        let media_item = MediaItem {
            id: track.id.clone(),
            title: track.name.clone(),
            name: Some(track.name.clone()), // Frontend compatibility
            artist: track
                .album_artist
                .clone()
                .or_else(|| track.artists.as_ref().and_then(|a| a.first().cloned())),
            album: track.album_name.clone(),
            album_name: track.album_name.clone(), // Frontend compatibility
            album_id: track.album_id.clone(),
            artist_items: track.artist_items.clone(), // For clickable artist links
            artists: track.artists.clone(),           // Fallback artist info
            primary_image_tag: track.primary_image_tag.clone(), // For frontend image display
            image_id: track.primary_image_tag.clone(),
            item_type: Some(track.item_type.clone()), // Frontend compatibility
            playlist_id: None,
            duration: track.runtime_ticks.map(|t| t as f64 / 10_000_000.0),
            artwork_url: primary_image_tag_for_url.and_then(|tag| {
                track.album_id.as_ref().map(|album_id| {
                    repository.get_image_url(
                        album_id,
                        ImageType::Primary,
                        Some(ImageOptions {
                            max_width: Some(300),
                            tag: Some(tag),
                            ..Default::default()
                        }),
                    )
                })
            }),
            media_type: MediaType::Audio,
            source,
            video_codec: None,
            needs_transcoding: false,
            video_width: None,
            video_height: None,
            subtitles: vec![],
            series_id: None,
            server_id: None,
        };

        media_items.push(media_item);
    }

    // Add to queue at specified position
    let position = match request.position.as_str() {
        "next" => AddPosition::Next,
        _ => AddPosition::End,
    };

    let controller = player.0.lock().await;
    let queue = controller.queue();
    let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;

    queue_lock.add(media_items, position);

    let result = get_queue_status(&controller);
    drop(queue_lock);
    controller.emit_queue_changed();

    info!(
        "Successfully added {} tracks to queue",
        request.track_ids.len()
    );
    Ok(result)
}

#[tauri::command]
#[specta::specta]
pub async fn player_skip_to(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    index: usize,
) -> Result<PlayerStatus, String> {
    let controller = player.0.lock().await;

    // Prefer downloads that completed since the queue was built
    if let Err(e) = super::refresh_queue_local_sources(&controller, &db).await {
        log::warn!("[player_skip_to] Failed to refresh local sources: {}", e);
    }

    // Skip to the index and get the item to play
    let item = {
        let queue = controller.queue();
        let mut queue_lock = queue.lock().map_err(|e| e.to_string())?;
        queue_lock.skip_to(index).cloned().ok_or("Invalid index")?
    };

    // Play the item without modifying the queue
    controller.load_and_play(&item).map_err(|e| e.to_string())?;

    // Emit queue changed event
    controller.emit_queue_changed();

    Ok(get_player_status(&controller))
}
