//! Audio and video playback settings commands.
//!
//! TRACES: UR-022, UR-027, UR-031, UR-032, UR-033, UR-074 | DR-025, DR-030, DR-034, DR-035, DR-036, DR-162, IR-020

use std::sync::Arc;

use log::{info, warn};
use tauri::{Manager, State};

use super::{PlayerStateWrapper, VideoSettingsWrapper};
use crate::commands::storage::DatabaseWrapper;
use crate::player::AutoplaySettings;
use crate::settings::{AudioSettings, EqPreset, StreamingQuality, VideoSettings};
use crate::storage::db_service::{DatabaseService, Query, QueryParam};
use crate::utils::lock::MutexSafe;

/// `app_settings` key holding the persisted streaming bandwidth ceiling.
///
/// The cap is persisted (unlike the rest of `VideoSettings`, which is
/// process-lifetime state) because forgetting it is the one failure that costs
/// the user something real: a limit set for a metered connection that silently
/// reverts to uncapped on the next launch spends their data allowance without
/// ever showing them a changed setting.
///
/// TRACES: UR-074 | DR-162
const STREAMING_QUALITY_KEY: &str = "streaming_quality";

#[tauri::command]
#[specta::specta]
pub async fn player_set_audio_settings(
    player: State<'_, PlayerStateWrapper>,
    settings: AudioSettings,
) -> Result<AudioSettings, String> {
    // Validate/normalise domain values before applying: clamp crossfade to its
    // range and normalise the equalizer band vector (length + gain clamps).
    let validated = settings
        .with_crossfade_clamped()
        .with_equalizer_normalised();
    let mut controller = player.0.lock().await;
    controller
        .set_audio_settings(&validated)
        .map_err(|e| e.to_string())?;
    Ok(controller.audio_settings())
}

/// The built-in equalizer presets and their per-band gain curves (dB), for the
/// settings UI. The curve numbers are domain data defined by the band layout,
/// so the frontend reads them here rather than encoding them.
///
/// TRACES: UR-027 | DR-030
#[tauri::command]
#[specta::specta]
pub async fn player_get_eq_presets() -> Result<Vec<(EqPreset, Vec<f32>)>, String> {
    Ok(EqPreset::ALL
        .iter()
        .map(|p| (*p, p.gains().to_vec()))
        .collect())
}

#[tauri::command]
#[specta::specta]
pub async fn player_get_audio_settings(
    player: State<'_, PlayerStateWrapper>,
) -> Result<AudioSettings, String> {
    let controller = player.0.lock().await;
    Ok(controller.audio_settings())
}

#[tauri::command]
#[specta::specta]
pub async fn player_set_video_settings(
    video_settings: State<'_, VideoSettingsWrapper>,
    player: State<'_, PlayerStateWrapper>,
    db: State<'_, DatabaseWrapper>,
    settings: VideoSettings,
) -> Result<VideoSettings, String> {
    let validated = settings.with_countdown_clamped();
    {
        let mut current = video_settings.0.lock().map_err(|e| e.to_string())?;
        *current = validated.clone();
    } // Drop MutexGuard before await

    // The bandwidth ceiling is read by the repository's URL builders and by the
    // PlaybackInfo negotiation, neither of which can see this wrapper.
    // TRACES: UR-074 | DR-162
    crate::repository::online::set_streaming_quality(validated.streaming_quality);
    persist_streaming_quality(&db, validated.streaming_quality).await;

    // Sync to PlayerController's autoplay settings so on_playback_ended() uses current values
    let controller = player.0.lock().await;
    controller.set_autoplay_settings(AutoplaySettings {
        enabled: validated.auto_play_next_episode,
        countdown_seconds: validated.auto_play_countdown_seconds,
        max_episodes: validated.auto_play_max_episodes,
    });

    Ok(validated)
}

/// The bandwidth ceilings the quality picker may offer, each with the label and
/// one-line detail to show for it, highest first.
///
/// The ladder and its numbers are Jellyfin encoding domain vocabulary, so the
/// frontend reads them here rather than encoding them — the same arrangement as
/// [`player_get_eq_presets`].
///
/// TRACES: UR-074 | DR-162
#[tauri::command]
#[specta::specta]
pub async fn player_get_streaming_qualities(
) -> Result<Vec<(StreamingQuality, String, String)>, String> {
    Ok(StreamingQuality::ALL
        .iter()
        .map(|q| (*q, q.label().to_string(), q.detail().to_string()))
        .collect())
}

/// Write the ceiling to `app_settings`. Failure is logged, not returned: the
/// setting has already been applied in memory, and refusing the whole call
/// because the write failed would leave the UI showing a cap that *is* active.
///
/// TRACES: UR-074 | DR-162
async fn persist_streaming_quality(db: &State<'_, DatabaseWrapper>, quality: StreamingQuality) {
    let db_service = {
        let database = db.0.lock_safe();
        Arc::new(database.service())
    };

    let encoded = match serde_json::to_string(&quality) {
        Ok(value) => value,
        Err(e) => {
            warn!("[VideoSettings] Failed to encode streaming quality: {}", e);
            return;
        }
    };

    let query = Query::with_params(
        "INSERT OR REPLACE INTO app_settings (key, value, updated_at)
         VALUES (?, ?, CURRENT_TIMESTAMP)",
        vec![
            QueryParam::String(STREAMING_QUALITY_KEY.to_string()),
            QueryParam::String(encoded),
        ],
    );

    if let Err(e) = db_service.execute(query).await {
        warn!("[VideoSettings] Failed to persist streaming quality: {}", e);
    }
}

/// Restore the persisted bandwidth ceiling at startup, into both the repository
/// (which enforces it) and `VideoSettings` (which the settings UI reads).
///
/// Called from the Tauri `setup` hook. A missing or unreadable row leaves the
/// default — uncapped — in place, so a database problem degrades to the old
/// behaviour rather than to an arbitrary limit.
///
/// TRACES: UR-074 | DR-162
pub async fn restore_streaming_quality(app: &tauri::AppHandle) {
    let db_service = {
        let Some(db) = app.try_state::<DatabaseWrapper>() else {
            warn!("[VideoSettings] No database available; streaming quality stays uncapped");
            return;
        };
        let database = db.0.lock_safe();
        Arc::new(database.service())
    };

    let query = Query::with_params(
        "SELECT value FROM app_settings WHERE key = ?",
        vec![QueryParam::String(STREAMING_QUALITY_KEY.to_string())],
    );

    let stored: Option<String> = match db_service.query_optional(query, |row| row.get(0)).await {
        Ok(value) => value,
        Err(e) => {
            warn!("[VideoSettings] Failed to read streaming quality: {}", e);
            return;
        }
    };

    let Some(stored) = stored else { return };
    let quality: StreamingQuality = match serde_json::from_str(&stored) {
        Ok(quality) => quality,
        Err(e) => {
            warn!(
                "[VideoSettings] Ignoring unrecognised persisted streaming quality {:?}: {}",
                stored, e
            );
            return;
        }
    };

    crate::repository::online::set_streaming_quality(quality);
    if let Some(video_settings) = app.try_state::<VideoSettingsWrapper>() {
        video_settings.0.lock_safe().streaming_quality = quality;
    }
    info!(
        "[VideoSettings] Restored streaming quality cap: {}",
        quality.label()
    );
}

#[tauri::command]
#[specta::specta]
pub async fn player_get_video_settings(
    video_settings: State<'_, VideoSettingsWrapper>,
) -> Result<VideoSettings, String> {
    let current = video_settings.0.lock().map_err(|e| e.to_string())?;
    Ok(current.clone())
}
