use super::{chat::create_modal_response, commands};
use crate::{
  HttpKey,
  commands::{audio::queue::init_track_queues, fun::roll::{format_roll, roll_dice, send_roll_message}},
};
use serenity::{
  all::{
    CreateInteractionResponse,
    EditInteractionResponse,
    Interaction,
    ResumedEvent,
    UnavailableGuild,
    UserId,
  },
  async_trait,
  model::{channel::Message, gateway::Ready},
  prelude::*,
};
use siren_core::{
  data::guilds::GuildCache,
  utils::{a_or_an, number_to_words},
};
use songbird::Songbird;
use std::sync::{Arc, OnceLock};

pub struct BotHandler {
  pub force_register: bool,
}

static REGISTERED: OnceLock<bool> = OnceLock::new();
static SONGBIRD: OnceLock<Arc<Songbird>> = OnceLock::new();
static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();

pub fn get_songbird() -> &'static Arc<Songbird> {
  SONGBIRD.get().unwrap()
}

pub fn get_client() -> &'static reqwest::Client {
  CLIENT.get().unwrap()
}

impl BotHandler {
  pub fn new(force_register: bool) -> Self {
    Self { force_register }
  }
}

#[async_trait]
impl EventHandler for BotHandler {
  async fn message(&self, _ctx: Context, msg: Message) {
    // Ignore bot messages
    if msg.author.bot {
      return;
    }

    // Handle direct messages
    if msg.guild_id.is_none() {
      log::trace!("Received DM from {}: {}", msg.author, msg.content);
    }
  }

  async fn ready(&self, ctx: Context, ready: Ready) {
    if ready.guilds.is_empty() {
      log::warn!("No ready guilds found");
    }

    // Initialise the track-queue metadata store (idempotent)
    init_track_queues();

    if SONGBIRD.get().is_none() {
      let songbird = songbird::get(&ctx).await.unwrap();
      SONGBIRD
        .set(songbird.clone())
        .expect("Songbird value could not be set");
    }
    if CLIENT.get().is_none() {
      let http_client = {
        let data = ctx.data.read().await;
        data
          .get::<HttpKey>()
          .cloned()
          .expect("Guaranteed to exist in the typemap.")
      };
      CLIENT.set(http_client).ok();
    }

    // Update registered to prevent reloading the commands
    if REGISTERED.get().is_some() {
      return;
    } else {
      REGISTERED.set(true).ok();
    }

    log::debug!("Registering in {} guild(s)", ready.guilds.len());
    for guild in ready.guilds {
      update_guild_commands(&ctx, &guild, self.force_register).await;
    }
  }

  async fn resume(&self, _: Context, _: ResumedEvent) {
    log::trace!("Resumed");
  }

  async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
    if let Interaction::Command(command) = interaction {
      log::trace!(
        "<{}> Received command: {}",
        command.guild_id.unwrap(),
        command.data.name
      );
      match command.data.name.as_str() {
        // Match commands without returns
        "play" => commands::audio::play::run(&ctx, &command).await,
        "stop" => commands::audio::stop::run(&ctx, &command).await,
        "pause" => commands::audio::pause::run(&ctx, &command).await,
        "resume" => commands::audio::resume::run(&ctx, &command).await,
        "mute" => commands::audio::mute::run(&ctx, &command).await,
        "skip" => commands::audio::skip::run(&ctx, &command).await,
        "volume" => commands::audio::volume::run(&ctx, &command).await,
        "schedule" => commands::event::schedule::run(&ctx, &command).await,
        "roll" => commands::fun::roll::run(&ctx, &command).await,
        "requestroll" => commands::fun::request_roll::run(&ctx, &command).await,
        "ping" => commands::utility::ping::run(&ctx, &command).await,
        _ => {}
      }
    } else if let Interaction::Component(component) = interaction {
      log::trace!("Received COMPONENT");
      let custom_id = &component.data.custom_id;
      if custom_id.starts_with("request_dice_roll") {
        // Acknowledge the interaction
        if let Err(err) = component
          .create_response(ctx.http.clone(), CreateInteractionResponse::Acknowledge)
          .await
        {
          log::error!("Could not create dice response: {}", err);
        };
        let parts = custom_id.split('|').collect::<Vec<&str>>();
        if parts.len() == 6 {
          let count = parts[1].parse().unwrap();
          let sides = parts[2].parse().unwrap();
          let modifier = parts[3].parse().unwrap();
          let result = roll_dice(count, sides, modifier);
          let response = format!("(Rolled {})", format_roll(count, sides, modifier));
          let user_id = UserId::from(parts[4].parse::<u64>().unwrap());
          let roller_id = component.user.id;
          let hidden: bool = parts[5].parse().unwrap();

          // Prepare the message based on visibility
          let new_message = if hidden {
            // For hidden rolls, only reveal "results sent" to the requester
            format!("🎲 Results sent to {}\n-# {}", user_id.mention(), response)
          } else {
            // For public rolls, show the roll result
            format!(
              "🎲 You rolled {} {}\n-# {}",
              a_or_an(&number_to_words(result)),
              result,
              response
            )
          };

          // Edit the message to update the text and remove buttons
          if let Err(err) = component
            .edit_response(
              ctx.http.clone(),
              EditInteractionResponse::new()
                .content(new_message)
                .components(Vec::new()),
            )
            .await
          {
            log::error!("Could not update dice roll message: {}", err);
          }

          // Send message to the requester
          send_roll_message(&ctx, result, user_id, roller_id, &response).await;
        } else {
          log::error!("Could not handle dice click: {}", custom_id);
        }
      }
    } else if let Interaction::Ping(_ping) = interaction {
      log::trace!("Received PING");
    } else if let Interaction::Autocomplete(_autocomplete) = interaction {
      log::trace!("Received AUTOCOMPLETE");
    } else if let Interaction::Modal(modal) = interaction {
      log::trace!("Received MODAL");
      create_modal_response(&ctx, &modal).await;
    }
  }
}

async fn update_guild_commands(ctx: &Context, guild: &UnavailableGuild, force_register: bool) {
  // List of commands to register for the guild
  let guild_commands = vec![
    commands::audio::play::register(),
    commands::audio::stop::register(),
    commands::audio::pause::register(),
    commands::audio::resume::register(),
    commands::audio::mute::register(),
    commands::audio::skip::register(),
    commands::audio::volume::register(),
    commands::event::schedule::register(),
    commands::fun::roll::register(),
    commands::fun::request_roll::register(),
    commands::utility::ping::register(),
  ];

  let guild_id = guild.id.get() as i64;
  let register_commands = match GuildCache::find_by_id(guild_id).await {
    Some(_) => force_register,
    None => {
      // If no guild cache is found, create a new one.
      let guild_cache = GuildCache {
        id: guild_id,
        name: guild.id.name(&ctx.cache),
        owner_id: None,
        volume: 100,
      };
      if let Err(err) = guild_cache.insert().await {
        log::error!("Could not insert guild cache: {err}");
      };
      true
    }
  };

  if register_commands {
    // Register the commands in the guild
    match guild.id.set_commands(&ctx.http, guild_commands).await {
      Ok(registered_commands) => {
        log::info!(
          "Registered {} commands for guild {}",
          registered_commands.len(),
          guild.id.get()
        );
      }
      Err(why) => {
        log::error!(
          "Could not register commands for guild {}: {:?}",
          guild.id.get(),
          why
        );
      }
    };
  } else {
    log::debug!("Guild {guild_id} is already registered");
  }
}
