use crate::{
  AppState,
  auth::SessionAuthorization,
  error::{Error, Result},
};
use axum::{
  Json,
  Router,
  extract::{Path, State},
  http::StatusCode,
  routing::{get, post},
};
use serde::{Deserialize, Serialize};
use siren_bot::{
  commands::audio::{
    join_voice_channel,
    pause::pause_track,
    play::enqueue_track,
    queue::{TrackInfo, get_is_paused, get_queue},
    resume::resume_track,
    skip::skip_track,
    stop::stop_track,
  },
  handler::get_songbird,
};
use std::sync::Arc;
use uuid::Uuid;

/// Routes that don't require a guild_id (nested at /api/audio)
pub fn get_routes() -> Router<Arc<AppState>> {
  Router::new().route("/guilds", get(list_guilds))
}

/// Routes that operate on a specific guild (nested at /api/audio/{guild_id})
pub fn get_guild_routes() -> Router<Arc<AppState>> {
  Router::new()
    .route("/play", post(play_audio))
    .route("/pause", post(pause_audio))
    .route("/resume", post(resume_audio))
    .route("/stop", post(stop_audio))
    .route("/skip", post(skip_audio))
    .route("/status", get(audio_status))
}

// ── Shared helpers ────────────────────────────────────────────────────────────

#[derive(Deserialize)]
struct PlayTrackRequest {
  url: String,
}

/// Resolve the Discord snowflake for a local user from `user_connections`.
/// Returns an error if the user has no linked Discord account.
async fn get_discord_snowflake(local_user_id: Uuid) -> Result<u64> {
  let pool = siren_core::data::pool();
  let provider_id: Option<String> = sqlx::query_scalar(
    "SELECT provider_user_id FROM user_connections \
     WHERE user_id = $1 AND provider = 'discord'",
  )
  .bind(local_user_id)
  .fetch_optional(pool)
  .await?;

  provider_id
    .and_then(|s| s.parse::<u64>().ok())
    .ok_or_else(|| Error::not_found("Discord account not connected".to_string()))
}

// ── GET /api/audio/guilds ─────────────────────────────────────────────────────

#[derive(Serialize)]
struct GuildInfo {
  id: String,
  name: String,
}

/// Returns all guilds the bot is currently in (from its Discord cache).
async fn list_guilds(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<GuildInfo>>> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;

  let guilds: Vec<GuildInfo> = state
    .cache
    .guilds()
    .into_iter()
    .filter_map(|guild_id| {
      state.cache.guild(guild_id).map(|g| GuildInfo {
        id: guild_id.get().to_string(),
        name: g.name.clone(),
      })
    })
    .collect();

  Ok(Json(guilds))
}

// ── POST /api/audio/{guild_id}/play ──────────────────────────────────────────

async fn play_audio(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
  Json(payload): Json<PlayTrackRequest>,
) -> Result<()> {
  log::debug!("Playing audio in guild: {}", guild_id);

  let session = session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;

  // Resolve Discord snowflake from the local user_id
  let discord_snowflake = get_discord_snowflake(session.user_id).await?;

  // Check if the user exists in the Discord cache
  let user_id = match state.cache.user(discord_snowflake) {
    Some(user) => user.id,
    None => {
      return Err(Error::not_found(
        "User not found in Discord cache".to_string(),
      ));
    }
  };

  // Validate if the guild exists in the cache
  let guild_id = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  // Play the track
  let manager = get_songbird();
  let _channel_id = join_voice_channel(&state.cache, manager, &guild_id, &user_id).await?;
  enqueue_track(manager, guild_id.to_owned(), &payload.url).await?;
  Ok(())
}

// ── POST /api/audio/{guild_id}/pause ─────────────────────────────────────────

async fn pause_audio(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
) -> Result<()> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  log::debug!("Pausing audio in guild: {}", guild_id);

  let guild_id = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  let manager = get_songbird();
  pause_track(manager, &guild_id).await?;
  Ok(())
}

// ── POST /api/audio/{guild_id}/resume ────────────────────────────────────────

async fn resume_audio(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
) -> Result<()> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  log::debug!("Resuming audio in guild: {}", guild_id);

  let guild_id = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  let manager = get_songbird();
  resume_track(manager, &guild_id).await?;
  Ok(())
}

// ── POST /api/audio/{guild_id}/stop ──────────────────────────────────────────

async fn stop_audio(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
) -> Result<()> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  log::debug!("Stopping audio in guild: {}", guild_id);

  let guild_id = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  let manager = get_songbird();
  stop_track(manager, &guild_id)
    .await
    .map_err(|e| Error::new(500, e))?;
  Ok(())
}

async fn skip_audio(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
) -> Result<()> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  log::debug!("<{}> Skipping audio", guild_id);

  let guild_id = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  let manager = get_songbird();
  skip_track(manager, &guild_id)
    .await
    .map_err(|e| Error::new(500, e))?;
  Ok(())
}

#[derive(Serialize)]
struct AudioStatus {
  voice_channel: Option<String>,
  is_paused: bool,
  current_track: Option<TrackInfo>,
  queue: Vec<TrackInfo>,
}

async fn audio_status(
  SessionAuthorization(session): SessionAuthorization,
  State(state): State<Arc<AppState>>,
  Path(guild_id): Path<u64>,
) -> Result<Json<AudioStatus>> {
  session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;

  let guild_id_snowflake = match state.cache.guild(guild_id) {
    Some(guild) => guild.id,
    None => return Err(Error::not_found("Guild not found".to_string())),
  };

  // ── Voice channel: look up the bot's own voice state + channel name from cache ──
  let bot_user_id = state.cache.current_user().id;
  let voice_channel = state
    .cache
    .guild(guild_id_snowflake)
    .and_then(|guild| {
      let ch_id = guild
        .voice_states
        .get(&bot_user_id)
        .and_then(|vs| vs.channel_id)?;
      guild.channels.get(&ch_id).map(|ch| ch.name.clone())
    });

  // ── Playback paused state (delegated to siren-bot to keep songbird internal) ──
  let is_paused = get_is_paused(guild_id).await;

  // ── Queue metadata from our store (index 0 = currently playing) ──
  let mut full_queue = get_queue(guild_id);
  let current_track = if !full_queue.is_empty() {
    Some(full_queue.remove(0))
  } else {
    None
  };

  Ok(Json(AudioStatus {
    voice_channel,
    is_paused,
    current_track,
    queue: full_queue,
  }))
}
