use dashmap::DashMap;
use dotenv::from_filename;
use reqwest::Client as HttpClient;
use serenity::{
  all::{ShardManager, UserId},
  http::Http,
  prelude::*,
};
use siren_api::{App, AppState};
use siren_bot::{HttpKey, handler::BotHandler};
use siren_core::{
  config::EnvironmentConfiguration,
  error::{Error, Result},
};
use songbird::{SerenityInit, Songbird};
use std::{collections::HashMap, sync::Arc};

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
  initialize_environment()?;
  let config = EnvironmentConfiguration::load()?;
  siren_core::data::initialize(&config).await?;

  let handler = BotHandler::new(config.force_command_register);
  let songbird = Songbird::serenity();
  let intents: GatewayIntents = GatewayIntents::all();

  let mut client = Client::builder(&config.discord_token, intents)
    .event_handler(handler)
    .register_songbird_with(Arc::clone(&songbird))
    .type_map_insert::<HttpKey>(HttpClient::new())
    .await
    .expect("Error creating client");

  let (bot_owner, bot_id) = get_bot_info(&client.http).await?;

  let app_state = AppState {
    client: HttpClient::new(),
    client_id: bot_id.to_string(),
    client_secret: config.discord_secret,
    base_url: config.api_base_url,
    discord_authorize_cache: Arc::new(Mutex::new(HashMap::new())),
    http: Arc::clone(&client.http),
    cache: Arc::clone(&client.cache),
    map_rooms: Arc::new(DashMap::new()),
  };

  log::debug!(
    "Starting Siren with ID: {bot_id} (Contact: {:?})",
    bot_owner
  );

  let shard_manager = Arc::clone(&client.shard_manager);
  tokio::spawn(async move {
    signal_shutdown(shard_manager).await;
  });

  tokio::spawn(App::new(app_state).serve());

  if let Err(why) = client.start_autosharded().await {
    log::error!("Client error: {why:?}");
  }

  Ok(())
}

fn initialize_environment() -> std::io::Result<()> {
  for entry in std::fs::read_dir(".")? {
    let entry = entry?;
    let path = entry.path();
    if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
      if file_name.starts_with(".env") && !file_name.ends_with(".example") && path.is_file() {
        if let Err(err) = from_filename(file_name) {
          eprintln!("Failed to load {}: {}", file_name, err);
        } else {
          println!("Loaded: {}", file_name);
        }
      }
    }
  }
  env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,siren=info"));
  Ok(())
}

async fn get_bot_info(http: &Http) -> Result<(Option<UserId>, UserId)> {
  match http.get_current_application_info().await {
    Ok(info) => {
      let bot_owner;
      if let Some(team) = info.team {
        bot_owner = Some(team.owner_user_id);
      } else if let Some(owner) = info.owner {
        bot_owner = Some(owner.id);
      } else {
        bot_owner = None;
      }
      match http.get_current_user().await {
        Ok(bot) => Ok((bot_owner, bot.id)),
        Err(why) => Err(Error::new(
          500,
          format!("Could not access the bot id: {why:?}"),
        )),
      }
    }
    Err(why) => Err(Error::new(
      500,
      format!("Could not access application info: {why:?}"),
    )),
  }
}

async fn signal_shutdown(shard_manager: Arc<ShardManager>) {
  tokio::signal::ctrl_c()
    .await
    .expect("Failed to listen for shutdown signal");
  shard_manager.shutdown_all().await;
  log::info!("Bot shutdown gracefully.");
}
