use std::str::FromStr;

use num_traits::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal;

/// 128-bit unsigned fixed point number with const generic precision.
///
/// Internally, Fixed128 is represented by u128. Most operations are optimized, using this raw int.
/// This will hold more variants that rust_decimal (which is just 96 bits) so using rust_decimal may lose precision.
///
/// Precision determines amount of decimal digits after point. Will support precision from 0 and up to 38.
/// Precision 0 means all nums will be ints. The larger precision is, the smaller number will be.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Fixed128<const PRECISION: u8>(pub u128);

impl<const PRECISION: u8> Fixed128<PRECISION> {
    pub const ONE: Self = Self(10u128.pow(PRECISION as u32));
    pub const MAX: Self = Self(u128::MAX);
    pub const ZERO: Self = Self(0);
    pub const MAX_INT: u128 = u128::MAX / Self::ONE.0;

    pub const fn from_raw(val: u128) -> Self {
        Self(val)
    }

    pub const fn into_raw(self) -> u128 {
        self.0
    }

    /// Convert int to Fixed128 `without` overflow checks
    pub const fn from_int(int: u128) -> Self {
        Self(int * Self::ONE.0)
    }

    /// Convert int to Fixed128 `with` overflow checks
    pub fn from_int_checked(int: u128) -> Option<Self> {
        int.checked_mul(Self::ONE.0).map(Self)
    }

    /// Convert float to Fixed128 `without` overflow checks. <br/>
    /// From_float will be lossless for <=1 with precision up to 21.
    /// Note that f64 support precise ints (not floats) up to 9007199254740992. <br/>
    /// If you using ints, prefer from_int instead.
    pub fn from_float(f: f64) -> Self {
        let int = f as u128;
        let frac = (f.fract() * (Self::ONE.0 as f64)) as u128; // We support up to 22 decimal points of precision

        Self(int * Self::ONE.0 + frac)
    }

    /// Convert float to Fixed128 `with` overflow checks. <br/>
    /// From_float will be lossless for <=1 with precision up to 21.
    /// Note that f64 support precise ints (not floats) up to 9007199254740992. <br/>
    /// If you using ints, prefer from_int instead.
    pub fn from_float_checked(f: f64) -> Option<Self> {
        let int = f as u128;
        let frac = (f.fract() * Self::ONE.0 as f64) as u128; // We support up to 22 decimal points of precision

        int.checked_mul(Self::ONE.0).and_then(|x| x.checked_add(frac)).map(Self)
    }

    /// Convert rust_decimal to Fixed128 `without` overflow checks. <br/>
    /// Note that Fixed128 uses 128 bits instead of 96 like rust_decimal. Decimal may lose precision. <br/>
    /// If you using ints, prefer from_int instead.
    pub fn from_decimal(f: rust_decimal::Decimal) -> Self {
        let int = f.to_u128().unwrap();
        let frac = (f.fract() * Decimal::from_u128(Self::ONE.0).unwrap()).to_u128().unwrap();

        Self(int * Self::ONE.0 + frac)
    }

    /// Convert rust_decimal to Fixed128 `with` overflow checks. <br/>
    /// Note that Fixed128 uses 128 bits instead of 96 like rust_decimal. Decimal may lose precision. <br/>
    /// If you using ints, prefer from_int instead.
    pub fn from_decimal_checked(f: rust_decimal::Decimal) -> Option<Self> {
        let int = f.to_u128()?;
        let frac = (f.fract() * Decimal::from_u128(Self::ONE.0)?).to_u128()?;

        int.checked_mul(Self::ONE.0).and_then(|x| x.checked_add(frac)).map(Self)
    }

    /// Construct Fixed128 from expression x/y.
    ///
    /// For example:
    /// ```
    /// use nintypes::utils::fixed::Fixed128;
    /// assert_eq!(&Fixed128::<18>::from_fraction(1,2).to_string(), "0.5")
    /// ```
    pub const fn from_fraction(x: u128, y: u128) -> Self {
        if x == 0 || y == 0 {
            return Self::ZERO;
        }
        // Divide first if x is larger than possible int
        if x >= Self::MAX_INT {
            return Self(Self::ONE.0 / y * x);
        }
        Self(x * Self::ONE.0 / y)
    }

    pub const fn int_part(self) -> u128 {
        self.0 / Self::ONE.0
    }
    pub const fn frac_part(self) -> u128 {
        self.0 % Self::ONE.0
    }
    pub fn int_part_decimal(self) -> Decimal {
        Decimal::new(self.int_part() as i64, 0)
    }
    pub fn frac_part_decimal(self) -> Decimal {
        Decimal::new(self.frac_part() as i64, PRECISION as u32)
    }

    pub fn into_decimal(self) -> Decimal {
        self.int_part_decimal() + self.frac_part_decimal()
    }

    pub const fn wrapping_add(self, rhs: Self) -> Self {
        Self(self.0.wrapping_add(rhs.0))
    }
    pub const fn wrapping_sub(self, rhs: Self) -> Self {
        Self(self.0.wrapping_sub(rhs.0))
    }

    pub const fn saturating_add(self, rhs: Self) -> Self {
        Self(self.0.saturating_add(rhs.0))
    }
    pub const fn saturating_sub(self, rhs: Self) -> Self {
        Self(self.0.saturating_sub(rhs.0))
    }

    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
        if let Some(x) = self.0.checked_add(rhs.0) {
            Some(Self(x))
        } else {
            None
        }
    }
    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
        if let Some(x) = self.0.checked_sub(rhs.0) {
            Some(Self(x))
        } else {
            None
        }
    }

    pub const fn scale(self) -> u8 {
        let mut frac = self.frac_part();
        let mut c = 0;

        if frac == 0 {
            return 0;
        }

        while frac % 10 == 0 {
            frac /= 10;
            c += 1;
        }

        PRECISION - c
    }

    pub fn with_precision<const NEW_PRECISION: u8>(self) -> Fixed128<NEW_PRECISION> {
        if PRECISION >= NEW_PRECISION {
            return Fixed128(self.0 / const { 10u128.pow(PRECISION.saturating_sub(NEW_PRECISION) as u32) });
        }

        Fixed128(self.0 * const { 10u128.pow(NEW_PRECISION.saturating_sub(PRECISION) as u32) })
    }
    pub fn with_checked_precision<const NEW_PRECISION: u8>(self) -> Option<Fixed128<NEW_PRECISION>> {
        if PRECISION >= NEW_PRECISION {
            if self.scale() > NEW_PRECISION {
                return None;
            }
            return Some(Fixed128(self.0 / const { 10u128.pow(PRECISION.saturating_sub(NEW_PRECISION) as u32) }));
        }

        Some(Fixed128(self.0 * const { 10u128.pow(NEW_PRECISION.saturating_sub(PRECISION) as u32) }))
    }

    /// Parse string with possible loss of precision if too many numbers is specified. <br/>
    /// Regular from_str will return `Err(Fixed128ParseErr::Loss)`
    pub fn from_str_lossy(s: &str) -> Result<Self, Fixed128ParseErr> {
        if let Some((int, frac)) = s.split_once('.') {
            let mut frac = frac.to_owned();

            let precision_diff = (PRECISION as isize) - (frac.len() as isize);
            if precision_diff > 0 {
                frac.push_str(&"0".repeat(precision_diff as usize));
            } else {
                frac = frac[..PRECISION as usize].to_owned();
            }

            let int = if int.is_empty() { 0 } else { int.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)? };
            let frac = if frac.is_empty() { 0 } else { frac.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)? };

            if int > const { u128::MAX / Self::ONE.0 } {
                return Err(Fixed128ParseErr::TooLarge);
            }

            return Ok(Self(int * Self::ONE.0 + frac));
        }

        let int = s.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)?;

        if int > const { u128::MAX / Self::ONE.0 } {
            return Err(Fixed128ParseErr::TooLarge);
        }

        Ok(Self::from_int(int))
    }
}

impl<const PRECISION: u8> std::fmt::Display for Fixed128<PRECISION> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let int = self.int_part();
        let mut frac = self.frac_part().to_string();
        frac.insert_str(0, &"0".repeat((PRECISION as usize).saturating_sub(frac.len())));
        let frac = frac.trim_end_matches('0');
        if frac.is_empty() {
            return write!(f, "{int}");
        }
        write!(f, "{int}.{frac}")
    }
}

impl<const PRECISION: u8> std::fmt::Debug for Fixed128<PRECISION> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(self, f)
    }
}

#[derive(Debug, thiserror::Error)]
pub enum Fixed128ParseErr {
    #[error("Unsupported chars")]
    InvalidChars,

    #[error("Fixed128 will not support numbers so large")]
    TooLarge,

    #[error("Not enough precision")]
    Loss,
}

impl<const PRECISION: u8> FromStr for Fixed128<PRECISION> {
    type Err = Fixed128ParseErr;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some((int, frac)) = s.split_once('.') {
            let mut frac = frac.to_owned();

            let precision_diff = (PRECISION as isize) - (frac.len() as isize);
            if precision_diff > 0 {
                frac.push_str(&"0".repeat(precision_diff as usize));
            } else {
                return Err(Fixed128ParseErr::Loss);
            }

            let int = if int.is_empty() { 0 } else { int.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)? };
            let frac = if frac.is_empty() { 0 } else { frac.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)? };

            if int > const { u128::MAX / Self::ONE.0 } {
                return Err(Fixed128ParseErr::TooLarge);
            }

            return Ok(Self(int * Self::ONE.0 + frac));
        }

        let int = s.parse::<u128>().map_err(|_| Fixed128ParseErr::InvalidChars)?;

        if int > const { u128::MAX / Self::ONE.0 } {
            return Err(Fixed128ParseErr::TooLarge);
        }

        Ok(Self::from_int(int))
    }
}

impl<const PRECISION: u8> std::ops::Add for Fixed128<PRECISION> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0)
    }
}

impl<const PRECISION: u8> std::ops::AddAssign for Fixed128<PRECISION> {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
    }
}

impl<const PRECISION: u8> std::ops::Sub for Fixed128<PRECISION> {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Self(self.0 - rhs.0)
    }
}

impl<const PRECISION: u8> std::ops::SubAssign for Fixed128<PRECISION> {
    fn sub_assign(&mut self, rhs: Self) {
        self.0 -= rhs.0;
    }
}

impl<const PRECISION: u8> std::ops::Mul for Fixed128<PRECISION> {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let a = primitive_types::U256::from(self.0);
        let b = primitive_types::U256::from(rhs.0);

        Self(((a * b) / primitive_types::U256::from(Self::ONE.0)).low_u128())
    }
}

impl<const PRECISION: u8> std::ops::MulAssign for Fixed128<PRECISION> {
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl<const PRECISION: u8> std::ops::Mul<u64> for Fixed128<PRECISION> {
    type Output = Self;

    fn mul(self, rhs: u64) -> Self::Output {
        Self(self.0 * rhs as u128)
    }
}

impl<const PRECISION: u8> std::ops::MulAssign<u64> for Fixed128<PRECISION> {
    fn mul_assign(&mut self, rhs: u64) {
        self.0 *= rhs as u128;
    }
}

impl<const PRECISION: u8> std::ops::Div<u64> for Fixed128<PRECISION> {
    type Output = Self;

    fn div(self, rhs: u64) -> Self::Output {
        Self(self.0 / rhs as u128)
    }
}
impl<const PRECISION: u8> std::ops::DivAssign<u64> for Fixed128<PRECISION> {
    fn div_assign(&mut self, rhs: u64) {
        self.0 /= rhs as u128;
    }
}

impl<const PRECISION: u8> serde::Serialize for Fixed128<PRECISION> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de, const PRECISION: u8> serde::Deserialize<'de> for Fixed128<PRECISION> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        String::deserialize(deserializer)?.parse::<Self>().map_err(serde::de::Error::custom)
    }
}

impl<const PRECISION: u8> num_traits::Zero for Fixed128<PRECISION> {
    fn zero() -> Self {
        Self(0)
    }

    fn is_zero(&self) -> bool {
        self.0.is_zero()
    }
}

impl<const PRECISION: u8> From<u128> for Fixed128<PRECISION> {
    fn from(value: u128) -> Self {
        Self::from_int(value)
    }
}
impl<const PRECISION: u8> From<u64> for Fixed128<PRECISION> {
    fn from(value: u64) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<u32> for Fixed128<PRECISION> {
    fn from(value: u32) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<u16> for Fixed128<PRECISION> {
    fn from(value: u16) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<u8> for Fixed128<PRECISION> {
    fn from(value: u8) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<i128> for Fixed128<PRECISION> {
    fn from(value: i128) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<i64> for Fixed128<PRECISION> {
    fn from(value: i64) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<i32> for Fixed128<PRECISION> {
    fn from(value: i32) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<i16> for Fixed128<PRECISION> {
    fn from(value: i16) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<i8> for Fixed128<PRECISION> {
    fn from(value: i8) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<bool> for Fixed128<PRECISION> {
    fn from(value: bool) -> Self {
        Self::from_int(value as u128)
    }
}
impl<const PRECISION: u8> From<f64> for Fixed128<PRECISION> {
    fn from(value: f64) -> Self {
        Self::from_float(value)
    }
}

impl<const PRECISION: u8> From<f32> for Fixed128<PRECISION> {
    fn from(value: f32) -> Self {
        Self::from_float(value as f64)
    }
}

impl<const PRECISION: u8> From<rust_decimal::Decimal> for Fixed128<PRECISION> {
    fn from(value: rust_decimal::Decimal) -> Self {
        Self::from_decimal(value)
    }
}

impl<const PRECISION: u8> std::hash::Hash for Fixed128<PRECISION> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}
