//! Sleep-timer and autoplay commands.
//!
//! TRACES: UR-026, UR-023 | DR-029, DR-047, DR-049
//!
//! Thin command adapters over `PlayerController`'s sleep-timer and autoplay
//! logic, plus persistence of autoplay settings to the database.

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

use super::{
    create_media_item, get_player_status, DatabaseWrapper, PlayItemRequest, PlayerStateWrapper,
    PlayerStatus,
};
use crate::player::{AutoplaySettings, SleepTimerMode, SleepTimerState};
use crate::storage::db_service::{DatabaseService, Query, QueryParam};

// ===== Sleep Timer Commands =====

/// Set sleep timer mode
#[tauri::command]
#[specta::specta]
pub async fn player_set_sleep_timer(
    player: State<'_, PlayerStateWrapper>,
    mode: SleepTimerMode,
) -> Result<SleepTimerState, String> {
    let controller = player.0.lock().await;
    controller.set_sleep_timer(mode);
    Ok(controller.sleep_timer_state())
}

/// Cancel sleep timer
#[tauri::command]
#[specta::specta]
pub async fn player_cancel_sleep_timer(
    player: State<'_, PlayerStateWrapper>,
) -> Result<SleepTimerState, String> {
    let controller = player.0.lock().await;
    controller.cancel_sleep_timer();
    Ok(controller.sleep_timer_state())
}

/// Get current sleep timer state
#[tauri::command]
#[specta::specta]
pub async fn player_get_sleep_timer(
    player: State<'_, PlayerStateWrapper>,
) -> Result<SleepTimerState, String> {
    let controller = player.0.lock().await;
    Ok(controller.sleep_timer_state())
}

// ===== Autoplay Commands =====

/// Get autoplay settings
#[tauri::command]
#[specta::specta]
pub async fn player_get_autoplay_settings(
    player: State<'_, PlayerStateWrapper>,
) -> Result<AutoplaySettings, String> {
    let controller = player.0.lock().await;
    Ok(controller.autoplay_settings())
}

/// Set autoplay settings and persist to database
#[tauri::command]
#[specta::specta]
pub async fn player_set_autoplay_settings(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    user_id: String,
    settings: AutoplaySettings,
) -> Result<AutoplaySettings, String> {
    let validated = settings.with_validated_countdown();

    // Set in controller
    {
        let controller = player.0.lock().await;
        controller.set_autoplay_settings(validated.clone());
    }

    // Persist to database
    let db_service = {
        let database = db.0.lock().map_err(|e| e.to_string())?;
        Arc::new(database.service())
    };

    let query = Query::with_params(
        "INSERT INTO user_player_settings (user_id, autoplay_next_episode, autoplay_countdown_seconds, autoplay_max_episodes, updated_at)
         VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
         ON CONFLICT(user_id) DO UPDATE SET
            autoplay_next_episode = excluded.autoplay_next_episode,
            autoplay_countdown_seconds = excluded.autoplay_countdown_seconds,
            autoplay_max_episodes = excluded.autoplay_max_episodes,
            updated_at = CURRENT_TIMESTAMP",
        vec![
            QueryParam::String(user_id),
            QueryParam::Int(if validated.enabled { 1 } else { 0 }),
            QueryParam::Int(validated.countdown_seconds as i32),
            QueryParam::Int(validated.max_episodes as i32),
        ],
    );

    db_service.execute(query).await.map_err(|e| e.to_string())?;

    Ok(validated)
}

/// Cancel active autoplay countdown
#[tauri::command]
#[specta::specta]
pub async fn player_cancel_autoplay_countdown(
    player: State<'_, PlayerStateWrapper>,
) -> Result<(), String> {
    let controller = player.0.lock().await;
    controller.cancel_autoplay_countdown();
    Ok(())
}

/// Play next episode (user confirmed from popup)
#[tauri::command]
#[specta::specta]
pub async fn player_play_next_episode(
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    item: PlayItemRequest,
) -> Result<PlayerStatus, String> {
    // Convert request to MediaItem
    let media_item = create_media_item(item, Some(&db)).await?;

    let controller = player.0.lock().await;
    controller
        .play_item(media_item)
        .map_err(|e| e.to_string())?;

    Ok(get_player_status(&controller))
}

/// Handle playback ended event - triggers autoplay decision logic
/// This is called from:
/// - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video
/// - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed
/// - Android JNI callback also triggers this logic directly
#[tauri::command]
#[specta::specta]
pub async fn player_on_playback_ended(
    player: State<'_, PlayerStateWrapper>,
    repository_manager: State<'_, crate::commands::repository::RepositoryManagerWrapper>,
    db: State<'_, DatabaseWrapper>,
    item_id: Option<String>,
    repository_handle: Option<String>,
) -> Result<(), String> {
    use crate::player::autoplay::AutoplayDecision;
    use crate::player::PlayerStatusEvent;

    let controller_arc = player.0.clone();

    // Run autoplay decision logic
    // If item_id is provided (HTML5 video case), use the video-specific path
    // that bypasses the backend queue and stale end_reason
    let decision = {
        let controller = controller_arc.lock().await;
        if let Some(ref id) = item_id {
            // Video path: need repository to look up episode info
            let repo = repository_handle
                .as_ref()
                .and_then(|handle| repository_manager.0.get(handle));
            if let Some(repo) = repo {
                controller.on_video_playback_ended(id, repo).await?
            } else {
                log::warn!(
                    "[Autoplay] No repository available for video autoplay (itemId: {})",
                    id
                );
                AutoplayDecision::Stop
            }
        } else {
            controller.on_playback_ended().await?
        }
    };

    // Handle the decision
    match decision {
        AutoplayDecision::Stop => {
            log::info!("[Autoplay] Decision: Stop playback");
            let controller = controller_arc.lock().await;
            // Clear the queue so the frontend's currentQueueItem becomes null and
            // the mini player hides. Without this, the queue still holds the last
            // track and the bar would linger (the frontend keeps the bar visible
            // through transient idle blips as long as a queue item exists).
            controller.clear_queue();
            controller.emit_queue_changed();
            if let Some(emitter) = controller.event_emitter() {
                // Emit StateChanged to idle to clear the current media from mini player
                // Note: Do NOT emit PlaybackEnded here - it would cause an infinite loop
                // (frontend receives PlaybackEnded → calls player_on_playback_ended → Stop → PlaybackEnded → ...)
                emitter.emit(PlayerStatusEvent::StateChanged {
                    state: "idle".to_string(),
                    media_id: None,
                });
            }
        }
        AutoplayDecision::AdvanceToNext => {
            log::info!("[Autoplay] Decision: Advance to next track");
            // Advance to next track in queue
            let controller = controller_arc.lock().await;
            // Prefer downloads that completed since the queue was built (e.g.
            // preloaded upcoming tracks) over continuing to stream.
            if let Err(e) = super::refresh_queue_local_sources(&controller, &db).await {
                log::warn!("[Autoplay] Failed to refresh local sources: {}", e);
            }
            if let Err(e) = controller.next() {
                log::error!("[Autoplay] Failed to advance to next track: {}", e);
                // Emit PlaybackEnded event on error
                if let Some(emitter) = controller.event_emitter() {
                    emitter.emit(PlayerStatusEvent::PlaybackEnded);
                }
            } else {
                // Emit queue changed event so frontend updates UI with new current track
                controller.emit_queue_changed();
            }
        }
        AutoplayDecision::ShowNextEpisodePopup {
            current_episode,
            next_episode,
            countdown_seconds,
            auto_advance,
        } => {
            log::info!(
                "[Autoplay] Decision: Show next episode popup (countdown: {}s, auto: {})",
                countdown_seconds,
                auto_advance
            );

            // Emit popup event to frontend
            if let Some(emitter) = controller_arc.lock().await.event_emitter() {
                emitter.emit(PlayerStatusEvent::ShowNextEpisodePopup {
                    current_episode: current_episode.clone(),
                    next_episode: next_episode.clone(),
                    countdown_seconds,
                    auto_advance,
                });
            }

            // Start countdown if auto_advance enabled
            if auto_advance {
                controller_arc
                    .lock()
                    .await
                    .start_autoplay_countdown(next_episode, countdown_seconds);
            }
        }
    }

    Ok(())
}

// ===== HTML5 video state-report commands =====
//
// On platforms where video renders in the webview (Linux WebKitGTK HTML5
// <video>), the real player lives outside the native backend, so the frontend
// HTML5 adapter reports DOM events back through these commands. The controller
// re-emits them through the same PlayerStatusEvent pipeline the native backends
// use, keeping the Rust controller the single source of truth and the frontend
// player store fed from one place (playerEvents.ts) in both modes.

/// Report an HTML5 <video> state change (playing/paused/loading/stopped/idle).
#[tauri::command]
#[specta::specta]
pub async fn player_report_state(
    player: State<'_, PlayerStateWrapper>,
    state: String,
    media_id: Option<String>,
) -> Result<(), String> {
    let controller = player.0.lock().await;
    controller.report_html5_state(state, media_id);
    Ok(())
}

/// Report an HTML5 <video> position tick (seconds). The adapter should throttle
/// these to roughly match the native backends' ~250ms cadence.
#[tauri::command]
#[specta::specta]
pub async fn player_report_position(
    player: State<'_, PlayerStateWrapper>,
    position: f64,
    duration: f64,
) -> Result<(), String> {
    let controller = player.0.lock().await;
    controller.report_html5_position(position, duration);
    Ok(())
}

/// Report that the HTML5 <video> finished loading and knows its duration.
#[tauri::command]
#[specta::specta]
pub async fn player_report_media_loaded(
    player: State<'_, PlayerStateWrapper>,
    duration: f64,
) -> Result<(), String> {
    let controller = player.0.lock().await;
    controller.report_html5_media_loaded(duration);
    Ok(())
}
