use lock_api::RawMutex;

use std::sync::Arc;

use rand::{seq::SliceRandom, thread_rng};

use super::super::prelude::*;

use rusqlite::params;

use serenity::prelude::*;
use serenity::voice::LockedAudio;
use serenity::{
    framework::standard::{macros::command, Args, CommandResult},
    model::channel::Message,
};

#[command]
fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
    if !channel_contains_author(ctx, msg) {
        println!(
            "user {} is not in the same voice channel as the bot",
            msg.author.name
        );
        return Ok(());
    }

    let mut data = ctx.data.write();
    let media = match data.get_mut::<Media>() {
        Some(media) => media,
        None => {
            display_error_ok!(msg.channel_id.say(&ctx.http, "could not find media data"));
            return Ok(());
        }
    };

    if args.len() == 0 {
        if !check_for_continue(media.song_mut()) {
            print_error!(msg
                .channel_id
                .say(&ctx.http, "Must provide a URL to a video or audio"));
        }
    } else if args.len() == 1 {
        let arg = match args.single::<String>() {
            Ok(arg) => arg,
            // can't happen, since we tested for length == 1
            Err(_) => return Ok(()),
        };

        if arg == "--local" {
            handle_local_request(media, ctx, msg)?;
        } else if arg.starts_with("http") {
            handle_http_request(media, ctx, msg, &arg)?;
        } else {
            handle_song_request(media, ctx, msg, &arg)?;
        }
    } else {
        print_error!(msg.channel_id.say(&ctx.http, "Unsupported argument list"));
    }

    Ok(())
}

fn check_for_continue(song_lock: &Option<LockedAudio>) -> bool {
    match song_lock {
        Some(song) => {
            let song_clone = song.clone();
            let mut audio_lock = song_clone.lock();

            audio_lock.play();
            true
        }
        None => false,
    }
}

fn check_join_channel<T: RawMutex>(
    manager_lock: &Arc<lock_api::Mutex<T, serenity::client::bridge::voice::ClientVoiceManager>>,
    ctx: &Context,
    msg: &Message,
) {
    let guild = check_result_return!(guild(ctx, msg));
    let guild_id = check_result_return!(guild_id(ctx, msg));

    let channel_id = guild
        .read()
        .voice_states
        .get(&msg.author.id)
        .and_then(|voice_state| voice_state.channel_id);

    let connect_to = match channel_id {
        Some(channel) => channel,
        None => {
            display_error!(msg.reply(ctx, "Not in a voice channel"));

            return;
        }
    };

    let mut manager = manager_lock.lock();
    manager.join(guild_id, connect_to);
}

fn append_songs(
    media: &mut MediaData,
    ctx: &Context,
    msg: &Message,
    mut source: Vec<Song>,
) -> Result<(), String> {
    media.playlist_mut().append(&mut source);

    if let Some(manager_lock) = ctx.data.read().get::<VoiceManager>().cloned() {
        check_join_channel(&manager_lock, ctx, msg);

        // let check_finished = {
        //     let channel_id = msg.channel_id;
        //     let manager_lock_clone = manager_lock.clone();

        //     Arc::new(move || {
        //         let callback = match media.next_callback.borrow().deref() {
        //             Some(callback) => callback.clone(),
        //             None => {
        //                 println!("next_callback not set!");
        //                 return;
        //             }
        //         };

        //         MediaData::next_song(&media_clone, channel_id, &manager_lock_clone, callback);
        //     })
        // };

        MediaData::start_playing(ctx, media, msg, &manager_lock);
    }

    Ok(())
}

fn handle_http_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
    url: &String,
) -> Result<(), String> {
    let mut names = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare("SELECT name FROM Vulva3 WHERE link = ?") {
            Ok(statement) => statement,
            Err(_) => return Err("failed preparing data base access".to_string()),
        };

        let rows = match stmt.query_map(&[url], |row| row.get(0) as rusqlite::Result<String>) {
            Ok(rows) => rows,
            Err(_) => return Err("failed querying rows".to_string()),
        };

        for name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => return Err("failed getting name from row".to_string()),
            };

            names.push(name);
        }
    }

    if names.len() > 0 {
        print_error!(msg.channel_id.say(&ctx.http, "song already loaded!"));

        append_songs(
            media,
            ctx,
            msg,
            names.iter().map(|n| Song { name: n.clone() }).collect(),
        )?;
    } else {
        let source = match youtube_dl(&url) {
            Ok(source) => source,
            Err(why) => {
                println!("Err starting source: {:?}", why);

                print_error!(msg
                    .channel_id
                    .say(&ctx.http, format!("Error using youtube-dl: {}", why)));

                return Ok(());
            }
        };

        let mut info = if source.len() == 1 {
            "Finished downloading song:".to_string()
        } else {
            "Finished downloading songs:".to_string()
        };

        {
            let sql = media.db();

            for song in &source {
                info = format!("{}\n\t{}", info, song.name);

                if sql
                    .execute(
                        "INSERT INTO Vulva3 (name, link)
                    VALUES (?1, ?2)",
                        params![song.name, url],
                    )
                    .is_err()
                {
                    return Err("failed inserting songs into db".to_string());
                }
            }
        }

        print_error!(msg.channel_id.say(&ctx.http, info));

        append_songs(media, ctx, msg, source)?;
    }

    Ok(())
}

fn handle_local_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
) -> Result<(), String> {
    let mut songs = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare("SELECT name FROM Vulva3") {
            Ok(statement) => statement,
            Err(_) => return Err("failed preparing data base access".to_string()),
        };

        let rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
            Ok(rows) => rows,
            Err(_) => return Err("failed querying rows".to_string()),
        };

        for name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => return Err("failed getting name from row".to_string()),
            };

            songs.push(Song { name });
        }
    }

    let mut rng = thread_rng();
    songs.shuffle(&mut rng);

    append_songs(media, ctx, msg, songs)?;

    Ok(())
}

fn handle_song_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
    pattern: &str,
) -> Result<(), String> {
    let mut songs = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare(&format!(
            "SELECT name FROM Vulva3 WHERE name LIKE '%{}%'",
            pattern
        )) {
            Ok(statement) => statement,
            Err(_) => return Err("failed preparing data base access".to_string()),
        };

        let rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
            Ok(rows) => rows,
            Err(_) => return Err("failed querying rows".to_string()),
        };

        for name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => return Err("failed getting name from row".to_string()),
            };

            songs.push(Song { name });
        }
    }

    if !songs.is_empty() {
        let mut rng = thread_rng();
        songs.shuffle(&mut rng);

        append_songs(media, ctx, msg, songs)?;
    } else {
        print_error!(msg
            .channel_id
            .say(&ctx.http, format!("no song found with pattern {}", pattern)));
    }

    Ok(())
}