use super::audio_state::AudioState;
use super::song::Song;

use lazy_static::lazy_static;
use serenity::{
    client::Context,
    framework::standard::{
        macros::{command, group},
        Args, CommandResult,
    },
    model::{channel::Message, id::GuildId},
};
use songbird::tracks::TrackCommand;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex;

use crate::util::{message_react, send_embed};

#[group]
#[commands(
    join,
    disconnect,
    play,
    skip,
    pause,
    resume,
    change_loop,
    volume,
    shuffle,
    clear,
    queue
)]
struct Audio;

lazy_static! {
    static ref AUDIO_STATES: Mutex<HashMap<GuildId, Arc<Mutex<AudioState>>>> = Mutex::new(HashMap::new());
}

async fn get_audio_state(ctx: &Context, msg: &Message) -> Option<Arc<Mutex<AudioState>>> {
    let guild = msg.guild(&ctx.cache).await.unwrap();
    let guild_id = guild.id;

    let mut audio_states = AUDIO_STATES.lock().await;
    let manager = songbird::get(ctx).await.unwrap();
    let channel_id = guild
        .voice_states
        .get(&msg.author.id)
        .and_then(|voice_state| voice_state.channel_id);

    let channel_id = match channel_id {
        Some(channel_id) => channel_id,
        None => {
            send_embed(ctx, msg, "Error: please be in a voice channel").await;
            return None;
        }
    };

    if let Some(call_locked) = manager.get(guild_id) {
        let call = call_locked.lock().await;

        if call.current_channel().is_none() {
            drop(call);
            match manager.remove(guild_id).await {
                Ok(_) => {}
                Err(err) => {
                    println!("Error leaving call: {:?}", err);
                    return None;
                }
            }
            audio_states.remove(&guild_id);
        }
    }

    let call_lock = match manager.get(guild_id) {
        Some(call) => call,
        None => {
            audio_states.remove(&guild_id);
            match manager.join(guild_id, channel_id).await {
                (call, Ok(_)) => call,
                (_, Err(err)) => {
                    println!("Error joining call: {:?}", err);
                    return None;
                }
            }
        }
    };

    let mut call = call_lock.lock().await;

    if call.current_channel() != Some(channel_id.into()) {
        if let Err(err) = call.join(channel_id.into()).await {
            println!("Error joining call: {:?}", err);
        }
    }

    match audio_states.get(&guild_id) {
        Some(state) => {
            let state = state.clone();
            AudioState::set_context(state.clone(), ctx, msg).await;
            Some(state)
        }
        None => {
            let audio_state = AudioState::new(manager.get(guild_id).unwrap(), ctx, msg);
            audio_states.insert(guild_id, audio_state.clone());
            Some(audio_state)
        }
    }
}

async fn remove_audio_state(ctx: &Context, msg: &Message) -> Result<(), String> {
    let guild = msg.guild(&ctx.cache).await.unwrap();
    let guild_id = guild.id;

    let mut audio_states = AUDIO_STATES.lock().await;
    let manager = songbird::get(ctx).await.unwrap();
    if let Err(_) = manager.remove(guild_id).await {
        return Err("Could not leave channel".to_string());
    }

    if audio_states.remove(&guild_id).is_some() {
        Ok(())
    } else {
        Err("bot is not currently active".to_string())
    }
}

#[command]
async fn join(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    if audio_state.is_some() {
        message_react(ctx, msg, "🥳").await;
    }

    Ok(())
}

#[command]
#[aliases("leave")]
async fn disconnect(ctx: &Context, msg: &Message) -> CommandResult {
    match remove_audio_state(ctx, msg).await {
        Ok(()) => message_react(ctx, msg, "👋").await,
        Err(why) => send_embed(ctx, msg, &format!("Error: {}", why)).await,
    };

    Ok(())
}

#[command]
async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
    let query = args.rest().to_string();

    message_react(ctx, msg, "🎶").await;

    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    let song = {
        let ctx = ctx.clone();
        let msg = msg.clone();
        tokio::spawn(async move {
            let song = Song::from_query(msg.author.clone(), query).await;
            match song {
                Ok(song) => {
                    message_react(&ctx, &msg, "✅").await;
                    Some(song)
                }
                Err(why) => {
                    message_react(&ctx, &msg, "❎").await;
                    send_embed(&ctx, &msg, &format!("Error: {}", why)).await;
                    None
                }
            }
        })
    };

    AudioState::add_audio(audio_state, song).await;

    Ok(())
}

#[command]
async fn skip(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    if let Err(why) = AudioState::send_track_command(audio_state, TrackCommand::Stop).await {
        send_embed(ctx, msg, &format!("Error: {}", why)).await;
    } else {
        message_react(ctx, msg, "↪").await;
    };
    Ok(())
}

#[command]
async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    if let Err(why) = AudioState::send_track_command(audio_state, TrackCommand::Pause).await {
        send_embed(ctx, msg, &format!("Error: {}", why)).await;
    } else {
        message_react(ctx, msg, "⏸").await;
    };
    Ok(())
}

#[command]
async fn resume(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    if let Err(why) = AudioState::send_track_command(audio_state, TrackCommand::Play).await {
        send_embed(ctx, msg, &format!("Error: {}", why)).await;
    } else {
        message_react(ctx, msg, "▶").await;
    };
    Ok(())
}

#[command]
async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    if let Err(why) = AudioState::shuffle(audio_state).await {
        send_embed(ctx, msg, &format!("Error: {}", why)).await;
    } else {
        message_react(ctx, msg, "🔀").await;
    };
    Ok(())
}

#[command]
async fn clear(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    if let Err(why) = AudioState::clear(audio_state.clone()).await {
        send_embed(ctx, msg, &format!("Error: {}", why)).await;
    } else {
        message_react(ctx, msg, "🗑").await;
    };

    Ok(())
}

#[command]
#[aliases("loop")]
async fn change_loop(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    match AudioState::change_looping(audio_state).await {
        Ok(true) => message_react(ctx, msg, "🔄").await,
        Ok(false) => message_react(ctx, msg, "➡").await,
        Err(why) => send_embed(ctx, msg, &format!("Error: {}", why)).await,
    };
    Ok(())
}

#[command]
#[aliases("vol")]
async fn volume(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    let volume: u8 = args.rest().parse()?;

    match AudioState::set_volume(audio_state, volume.into()).await {
        Ok(()) => message_react(ctx, msg, "🎶").await,
        Err(why) => send_embed(ctx, msg, &format!("Error: {}", why)).await,
    };
    Ok(())
}

#[command]
async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
    let audio_state = get_audio_state(ctx, msg).await;
    let audio_state = match audio_state {
        Some(audio_state) => audio_state,
        None => return Ok(()),
    };

    send_embed(ctx, msg, &AudioState::get_string(audio_state).await).await;

    Ok(())
}