use core::f64;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

use pumpkin_protocol::java::client::play::ArgumentType;
use pumpkin_util::text::TextComponent;

use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs;
use crate::server::Server;

use super::super::args::ArgumentConsumer;
use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser};

/// Consumes a single generic num, but only if it's in bounds.
pub struct BoundedNumArgumentConsumer<T: ToFromNumber> {
    min_inclusive: Option<T>,
    max_inclusive: Option<T>,
    name: Option<&'static str>,
}

impl<T: ToFromNumber> ArgumentConsumer for BoundedNumArgumentConsumer<T>
where
    Self: GetClientSideArgParser,
{
    fn consume<'a, 'b>(
        &'a self,
        _sender: &'a CommandSender,
        _server: &'a Server,
        args: &'b mut RawArgs<'a>,
    ) -> ConsumeResult<'a> {
        let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value);

        let result: Option<Arg<'a>> = s_opt
            // Replace args.pop()?.parse::<T>().ok()?
            .and_then(|s| s.parse::<T>().ok())
            .map(|x| {
                // Check Upper Bound (max_inclusive)
                if let Some(max) = self.max_inclusive
                    && x > max
                {
                    return Arg::Num(Err(NotInBounds::UpperBound(x.to_number(), max.to_number())));
                }

                // Check Lower Bound (min_inclusive)
                if let Some(min) = self.min_inclusive
                    && x < min
                {
                    return Arg::Num(Err(NotInBounds::LowerBound(x.to_number(), min.to_number())));
                }

                // Success case
                Arg::Num(Ok(x.to_number()))
            });

        Box::pin(async move { result })
    }
}

impl<'a, T: 'static + ToFromNumber> FindArg<'a> for BoundedNumArgumentConsumer<T> {
    type Data = Result<T, NotInBounds>;

    fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
        match args.get(name) {
            Some(Arg::Num(data)) => match data {
                Ok(num) => T::from_number(num).map_or_else(
                    || Err(CommandError::InvalidConsumption(Some(name.to_string()))),
                    |x| Ok(Ok(x)),
                ),
                Err(err) => Ok(Err(*err)),
            },
            _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
        }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum NotInBounds {
    /// Number is lower than the lower bound
    LowerBound(Number, Number),
    /// Number is higher then the upper bound
    UpperBound(Number, Number),
}

impl From<NotInBounds> for CommandError {
    fn from(value: NotInBounds) -> Self {
        match value {
            NotInBounds::LowerBound(val, min) => {
                let (key, min_text, val_text) = match val {
                    Number::F64(_) | Number::F32(_) => (
                        pumpkin_data::translation::java::ARGUMENT_DOUBLE_LOW,
                        min.to_string(),
                        val.to_string(),
                    ),
                    Number::I32(_) => (
                        pumpkin_data::translation::java::ARGUMENT_INTEGER_LOW,
                        min.to_string(),
                        val.to_string(),
                    ),
                    Number::I64(_) => (
                        pumpkin_data::translation::java::ARGUMENT_LONG_LOW,
                        min.to_string(),
                        val.to_string(),
                    ),
                };
                Self::CommandFailed(TextComponent::translate_cross(
                    key,
                    key,
                    [TextComponent::text(min_text), TextComponent::text(val_text)],
                ))
            }
            NotInBounds::UpperBound(val, max) => {
                let (key, max_text, val_text) = match val {
                    Number::F64(_) | Number::F32(_) => (
                        pumpkin_data::translation::java::ARGUMENT_DOUBLE_BIG,
                        max.to_string(),
                        val.to_string(),
                    ),
                    Number::I32(_) => (
                        pumpkin_data::translation::java::ARGUMENT_INTEGER_BIG,
                        max.to_string(),
                        val.to_string(),
                    ),
                    Number::I64(_) => (
                        pumpkin_data::translation::java::ARGUMENT_LONG_BIG,
                        max.to_string(),
                        val.to_string(),
                    ),
                };
                Self::CommandFailed(TextComponent::translate_cross(
                    key,
                    key,
                    [TextComponent::text(max_text), TextComponent::text(val_text)],
                ))
            }
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Number {
    F64(f64),
    F32(f32),
    I32(i32),
    I64(i64),
}

impl Number {
    #[must_use]
    pub const fn qualifier(&self) -> &'static str {
        match self {
            Self::F64(_) | Self::F32(_) => "Float",
            Self::I32(_) | Self::I64(_) => "Integer",
        }
    }
}

impl Display for Number {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::F64(v) => write!(f, "{v}"),
            Self::F32(v) => write!(f, "{v}"),
            Self::I32(v) => write!(f, "{v}"),
            Self::I64(v) => write!(f, "{v}"),
        }
    }
}

impl<T: ToFromNumber> BoundedNumArgumentConsumer<T> {
    #[must_use]
    pub const fn new() -> Self {
        Self {
            min_inclusive: None,
            max_inclusive: None,
            name: None,
        }
    }

    #[must_use]
    pub const fn min(mut self, min_inclusive: T) -> Self {
        self.min_inclusive = Some(min_inclusive);
        self
    }

    #[must_use]
    pub const fn max(mut self, max_inclusive: T) -> Self {
        self.max_inclusive = Some(max_inclusive);
        self
    }

    #[must_use]
    pub const fn name(mut self, name: &'static str) -> Self {
        self.name = Some(name);
        self
    }
}

pub trait ToFromNumber: PartialOrd + Copy + Send + Sync + FromStr {
    fn to_number(self) -> Number;
    fn from_number(arg: &Number) -> Option<Self>;
}

impl<T: ToFromNumber> Default for BoundedNumArgumentConsumer<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl ToFromNumber for f64 {
    fn to_number(self) -> Number {
        Number::F64(self)
    }

    fn from_number(arg: &Number) -> Option<Self> {
        match arg {
            Number::F64(x) => Some(*x),
            _ => None,
        }
    }
}

impl GetClientSideArgParser for BoundedNumArgumentConsumer<f64> {
    fn get_client_side_parser(&self) -> ArgumentType {
        ArgumentType::Double {
            min: self.min_inclusive,
            max: self.max_inclusive,
        }
    }

    fn get_client_side_suggestion_type_override(
        &self,
    ) -> Option<pumpkin_protocol::java::client::play::SuggestionProviders> {
        None
    }
}

impl ToFromNumber for f32 {
    fn to_number(self) -> Number {
        Number::F32(self)
    }

    fn from_number(arg: &Number) -> Option<Self> {
        match arg {
            Number::F32(x) => Some(*x),
            _ => None,
        }
    }
}

impl GetClientSideArgParser for BoundedNumArgumentConsumer<f32> {
    fn get_client_side_parser(&self) -> ArgumentType {
        ArgumentType::Float {
            min: self.min_inclusive,
            max: self.max_inclusive,
        }
    }

    fn get_client_side_suggestion_type_override(
        &self,
    ) -> Option<pumpkin_protocol::java::client::play::SuggestionProviders> {
        None
    }
}

impl ToFromNumber for i32 {
    fn to_number(self) -> Number {
        Number::I32(self)
    }

    fn from_number(arg: &Number) -> Option<Self> {
        match arg {
            Number::I32(x) => Some(*x),
            _ => None,
        }
    }
}

impl GetClientSideArgParser for BoundedNumArgumentConsumer<i32> {
    fn get_client_side_parser(&self) -> ArgumentType {
        ArgumentType::Integer {
            min: self.min_inclusive,
            max: self.max_inclusive,
        }
    }

    fn get_client_side_suggestion_type_override(
        &self,
    ) -> Option<pumpkin_protocol::java::client::play::SuggestionProviders> {
        None
    }
}

impl ToFromNumber for i64 {
    fn to_number(self) -> Number {
        Number::I64(self)
    }

    fn from_number(arg: &Number) -> Option<Self> {
        match arg {
            Number::I64(x) => Some(*x),
            _ => None,
        }
    }
}

impl GetClientSideArgParser for BoundedNumArgumentConsumer<i64> {
    fn get_client_side_parser(&self) -> ArgumentType {
        ArgumentType::Long {
            min: self.min_inclusive,
            max: self.max_inclusive,
        }
    }

    fn get_client_side_suggestion_type_override(
        &self,
    ) -> Option<pumpkin_protocol::java::client::play::SuggestionProviders> {
        None
    }
}

impl<T: ToFromNumber> DefaultNameArgConsumer for BoundedNumArgumentConsumer<T>
where
    Self: ArgumentConsumer,
{
    fn default_name(&self) -> &'static str {
        // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion
        self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.")
    }
}
