use crate::{
  chat::{create_message_response, edit_response},
  error::{Error, Result},
};
use rand::RngExt;
use serenity::all::{
  CommandInteraction,
  CommandOptionType,
  Context,
  CreateCommand,
  CreateCommandOption,
  CreateEmbed,
  CreateMessage,
  Mentionable,
  UserId,
};
use siren_core::utils::{a_or_an, number_to_words};

pub async fn run(ctx: &Context, command: &CommandInteraction) {
  // Check if the roll result is private
  let private = command
    .data
    .options
    .iter()
    .find(|opt| opt.name == "private")
    .and_then(|o| o.value.as_bool())
    .unwrap_or(true);

  // Retrieve the user if present
  let user = command
    .data
    .options
    .iter()
    .find(|opt| opt.name == "user")
    .and_then(|o| o.value.as_mentionable());

  create_message_response(ctx, command, "Rolling...".to_string(), private).await;

  let dice_string = match command
    .data
    .options
    .first()
    .and_then(|o| o.value.as_str())
    .map(|s| s.split_whitespace().collect::<String>())
  {
    Some(dice_value) => dice_value,
    None => {
      log::warn!("Missing or invalid dice option");
      let _ = edit_response(ctx, command, "Dice option is missing".to_string()).await;
      return;
    }
  };

  let dice = parse_dice(dice_string.as_str());
  match dice {
    Ok((count, sides, modifier)) => {
      let total = roll_dice(count, sides, modifier);
      let response = format!("(Rolled {})", format_roll(count, sides, modifier));

      match user {
        Some(id) => {
          let user_id = UserId::new(id.get());
          let roller_id = command.user.id;
          send_roll_message(ctx, total, user_id, roller_id, &response).await;
          edit_response(
            ctx,
            command,
            format!("Sending dice roll results to {}", &user_id.mention()),
          )
          .await;
        }
        None => edit_response(ctx, command, format!("🎲 {}\n-# {}", total, response)).await,
      };
      // Check for dice tracks
    }
    Err(why) => {
      edit_response(ctx, command, format!("Invalid dice string: {}", why)).await;
    }
  }
}

pub async fn send_roll_message(
  ctx: &Context,
  total: i32,
  user_id: UserId,
  roller_id: UserId,
  dice_string: &str,
) {
  // Create the dice roll embed
  let a = a_or_an(&number_to_words(total));
  let embed = CreateEmbed::new()
    .title("🎲 Received a dice roll! 🎲".to_string())
    .color(0x00FF00)
    .description(format!(
      "{} rolled {} **{}**\n-# *{}*",
      &roller_id.mention(),
      a,
      total,
      dice_string
    ));

  let message = CreateMessage::new().embed(embed);
  if let Err(err) = user_id.dm(ctx, message).await {
    log::error!("Could not send message: {}", err);
  }
}

pub fn format_roll(count: u32, sides: u32, modifier: i32) -> String {
  format!(
    "{}d{}{}",
    count,
    sides,
    if modifier > 0 {
      format!("+{}", modifier)
    } else if modifier < 0 {
      format!("-{}", modifier)
    } else {
      "".to_string()
    }
  )
}

pub fn roll_dice(count: u32, sides: u32, modifier: i32) -> i32 {
  let mut rolls = Vec::new();
  let mut total = modifier;
  for _ in 0..count {
    let roll = rand::rng().random_range(1..=sides as i32);
    total += roll;
    rolls.push(roll);
  }
  total
}

pub fn parse_dice(dice: &str) -> Result<(u32, u32, i32)> {
  // If the input is just a number (e.g., "20" or "6"), assume it's the number of sides
  if let Ok(n) = dice.parse::<u32>() {
    return Ok((1, n, 0)); // Assume 1 dice with 0 modifiers
  }

  // If the input starts with "d", assume it's shorthand for "1dX"
  let dice = if dice.starts_with("d") {
    format!("1{}", dice) // Prepend "1"
  } else {
    dice.to_string()
  };

  let mut parts = dice.split(['d', '+', '-'].as_ref());
  let mut positive_modifier = true;

  // Parse the dice count
  let count = match parts.next() {
    Some("") => 1, // Handle cases like "d6", assume 1 dice
    Some(c) => match c.parse::<u32>() {
      Ok(n) => n,
      Err(_) => return Err(Error::new(400, format!("Invalid dice count: {}", c))),
    },
    None => return Err(Error::new(400, format!("Invalid dice string: {}", dice))),
  };

  // Parse the number of sides
  let sides_part = parts
    .next()
    .ok_or_else(|| Error::new(400, format!("Invalid dice string: {}", dice)))?;
  let sides = match sides_part.parse::<u32>() {
    Ok(n) => {
      if [4, 6, 8, 10, 12, 20, 100].contains(&n) {
        n
      } else {
        return Err(Error::new(
          400,
          format!(
            "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}",
            n
          ),
        ));
      }
    }
    Err(_) => {
      return Err(Error::new(
        400,
        format!(
          "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}",
          sides_part
        ),
      ));
    }
  };

  // Determine if there's a modifier (+ or -)
  if dice.contains('+') {
    positive_modifier = true;
  } else if dice.contains('-') {
    positive_modifier = false;
  }

  // Parse the modifier, if present
  let modifier = match parts.next() {
    Some(m) => match m.parse::<i32>() {
      Ok(n) => {
        if positive_modifier {
          n
        } else {
          -n
        }
      }
      Err(_) => return Err(Error::new(400, format!("Invalid dice modifier: {}", m))),
    },
    None => 0, // No modifier found
  };

  Ok((count, sides, modifier))
}

pub fn register() -> CreateCommand {
  CreateCommand::new("roll")
    .description("Roll dice")
    .add_option(
      CreateCommandOption::new(CommandOptionType::String, "dice", "Dice to roll").required(true),
    )
    .add_option(
      CreateCommandOption::new(
        CommandOptionType::Boolean,
        "private",
        "Make the roll private (Default: True)",
      )
      .required(false),
    )
    .add_option(
      CreateCommandOption::new(
        CommandOptionType::Mentionable,
        "user",
        "User to receive the roll results",
      )
      .required(false),
    )
}
