use std::sync::Arc;

use log::{debug, warn, error};

use serenity::model::prelude::GuildId;
use serenity::{prelude::*, async_trait};
use serenity::builder::CreateApplicationCommand;
use serenity::model::application::interaction::application_command::ApplicationCommandInteraction;
use siren::ServiceError;
use songbird::{EventHandler, Songbird};

use crate::bot::commands::audio::{leave, add_song, get_songbird};
use crate::storage::guilds::QueryGuild;

use super::{create_response, edit_response, join_by_user};

pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) {
  // Get the track url
  let track_url = match command.data.options.get(0) {
    Some(t) => match &t.value {
      Some(v) => match v.as_str() {
        Some(s) => s.to_owned(),
        None => {
          warn!("Missing track option");
          if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await {
            error!("Failed to create response message: {}", why);
          }
          return;
        }
      }
      None => {
        warn!("Missing track option");
        if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await {
          error!("Failed to create response message: {}", why);
        }
        return;
      }
    }
    None => {
      warn!("Missing track option");
      if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await {
        error!("Failed to create response message: {}", why);
      }
      return;
    }
  };

  // Create the initial response
  if let Err(why) = create_response(&ctx, &command, format!("Processing command...")).await {
    error!("Failed to create response message: {}", why);
    return;
  }

  let manager = get_songbird(ctx).await;
  match join_by_user(&ctx.cache, manager,&command.guild_id, &command.user).await {
    Ok(_) => {
      let guild_id = match command.guild_id {
        Some(g) => g,
        None => {
          if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await {
            error!("Failed to edit response message: {}", why);
          }
          return;
        }
      };
      debug!("Play command executed with track: {:?}", track_url);
      let manager = get_songbird(ctx).await;
      match play_track(manager, guild_id, track_url).await {
        Ok(_) => {
          if let Err(why) = edit_response(&ctx, &command, "Playing track".to_string()).await {
            error!("Failed to edit response message: {}", why);
          }
        },
        Err(err) => {
          warn!("Failed to play track: {}", err);
          if let Err(why) = edit_response(&ctx, &command, format!("Failed to play track: {}", err)).await {
            error!("Failed to edit response message: {}", why);
          }
        }
      };
    },
    Err(err) => {
      warn!("{}", err);
      if let Err(why) = edit_response(&ctx, &command, format!("{}", err)).await {
        error!("Failed to edit response message: {}", why);
      }
    }
  }
}

pub async fn play_track(manager: Arc<Songbird>, guild_id: GuildId, track_url: String) -> Result<(), ServiceError> {
  if let Some(handler_lock) = manager.get(guild_id) {
    let is_queue_empty = {
      let call_handler = handler_lock.lock().await;
      call_handler.queue().is_empty()
    };
    let guild = QueryGuild::get(guild_id.0 as i64)?;
    match add_song(handler_lock.clone(), &track_url, is_queue_empty, Some(guild.volume as f32)).await {
      Ok(added_song) => {
        let track_title = added_song.title.unwrap();
        debug!("Added track: {}", track_title);
        let mut handler = handler_lock.lock().await;
        handler.remove_all_global_events();
        handler.add_global_event(songbird::Event::Track(songbird::TrackEvent::End), TrackEndNotifier { guild_id, call: manager })
      },
      Err(err) => {
        warn!("Failed to add song: {}", err);
        if let Err(why) = leave(manager, &Some(guild_id)).await {
          error!("Failed to leave voice channel: {}", why);
        }
        return Err(ServiceError { status: 422, message: err.to_string() })
      }
    }
  }
  Ok(())
}

pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
  command.name("play").description("Plays the given track").create_option(|option| { option
    .name("track")
    .description("The track to be played")
    .kind(serenity::model::prelude::command::CommandOptionType::String)
    .required(true)
  })
}

struct TrackEndNotifier {
  pub call: std::sync::Arc<songbird::Songbird>,
  pub guild_id: serenity::model::id::GuildId
}

#[async_trait]
impl EventHandler for TrackEndNotifier {
  async fn act(&self, ctx: &songbird::events::EventContext<'_>) -> Option<songbird::events::Event> {
    if let songbird::EventContext::Track(_track_list) = ctx {
      if let Some(call) = self.call.get(self.guild_id) {
        let mut handler = call.lock().await;
        if handler.queue().is_empty() {
          debug!("Queue is empty, leaving voice channel");
          handler.leave().await.unwrap();
        }
      }
    }
    None
  }
}
