use crate::expect::ops::utils::SerializableBound;
use crate::expect::ops::utils::SerializableBoundContains;
use crate::expect::ops::ExpectInteger;
use crate::expect_core::Context;
use crate::expect_core::ExpectOpError;
use crate::expect_core::ExpectOpResult;
use crate::internals::objects::IntegerObject;
use crate::JsonInteger;
use num::Zero;
use serde::Deserialize;
use serde::Serialize;
use std::fmt::Debug;
use std::fmt::Display;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExpectIntegerSubOp {
    InRange {
        min: SerializableBound<i64>,
        max: SerializableBound<i64>,
    },
    OutsideRange {
        min: SerializableBound<i64>,
        max: SerializableBound<i64>,
    },
    Zero,
    NotZero,
    Positive,
    Negative,

    GreaterThan {
        expected: JsonInteger,
    },
    GreaterThanEqual {
        expected: JsonInteger,
    },
    LessThan {
        expected: JsonInteger,
    },
    LessThanEqual {
        expected: JsonInteger,
    },
}

impl ExpectIntegerSubOp {
    pub(crate) fn on_i64(
        &self,
        parent: &ExpectInteger,
        context: &mut Context<'_>,
        received: i64,
    ) -> ExpectOpResult<()> {
        match *self {
            Self::InRange { min, max } => on_i64_in_range(parent, context, received, min, max),
            Self::OutsideRange { min, max } => {
                on_i64_outside_range(parent, context, received, min, max)
            }

            Self::Zero => on_zero(context, received),
            Self::NotZero => on_not_zero(context, received),

            Self::Positive => on_positive(parent, context, received),
            Self::Negative => on_negative(parent, context, received),

            Self::GreaterThan { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::gt,
                "greater than",
            ),
            Self::GreaterThanEqual { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::ge,
                "greater than equal",
            ),
            Self::LessThan { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::lt,
                "less than",
            ),
            Self::LessThanEqual { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::le,
                "less than equal",
            ),
        }
    }

    pub(crate) fn on_u64(
        &self,
        parent: &ExpectInteger,
        context: &mut Context<'_>,
        received: u64,
    ) -> ExpectOpResult<()> {
        match *self {
            Self::InRange { min, max } => on_u64_in_range(parent, context, received, min, max),
            Self::OutsideRange { min, max } => {
                on_u64_outside_range(parent, context, received, min, max)
            }

            Self::Zero => on_zero(context, received),
            Self::NotZero => on_not_zero(context, received),

            Self::Positive => on_positive(parent, context, received),
            Self::Negative => on_negative(parent, context, received),

            Self::GreaterThan { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::gt,
                "greater than",
            ),
            Self::GreaterThanEqual { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::ge,
                "greater than equal",
            ),
            Self::LessThan { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::lt,
                "less than",
            ),
            Self::LessThanEqual { expected: num } => on_comparison(
                parent,
                context,
                received.into(),
                num,
                JsonInteger::le,
                "less than equal",
            ),
        }
    }
}

fn on_i64_in_range(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: i64,
    min: SerializableBound<i64>,
    max: SerializableBound<i64>,
) -> ExpectOpResult<()> {
    if !SerializableBound::contains(min, max, received) {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is not in range
    expected {}..{}
    received {received}",
                min.as_lowerbound(),
                max,
            ),
        ));
    }

    Ok(())
}

fn on_u64_in_range(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: u64,
    min: SerializableBound<i64>,
    max: SerializableBound<i64>,
) -> ExpectOpResult<()> {
    if !SerializableBound::contains(min, max, received) {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is not in range
    expected {}..{}
    received {received}",
                min.as_lowerbound(),
                max,
            ),
        ));
    }

    Ok(())
}

fn on_i64_outside_range(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: i64,
    min: SerializableBound<i64>,
    max: SerializableBound<i64>,
) -> ExpectOpResult<()> {
    if SerializableBound::contains(min, max, received) {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is in range
    expected {}..{}
    received {received}",
                min.as_lowerbound(),
                max,
            ),
        ));
    }

    Ok(())
}

fn on_u64_outside_range(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: u64,
    min: SerializableBound<i64>,
    max: SerializableBound<i64>,
) -> ExpectOpResult<()> {
    if SerializableBound::contains(min, max, received) {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is in range
    expected {}..{}
    received {received}",
                min.as_lowerbound(),
                max,
            ),
        ));
    }

    Ok(())
}

fn on_zero<I>(context: &mut Context<'_>, received: I) -> ExpectOpResult<()>
where
    I: Zero + Into<IntegerObject>,
{
    if !received.is_zero() {
        return Err(ExpectOpError::IntegerIsNotZero {
            context: context.to_static(),
            received: received.into(),
        });
    }

    Ok(())
}

fn on_not_zero<I>(context: &mut Context<'_>, received: I) -> ExpectOpResult<()>
where
    I: Zero + Into<IntegerObject>,
{
    if received.is_zero() {
        return Err(ExpectOpError::IntegerIsZero {
            context: context.to_static(),
            received: received.into(),
        });
    }

    Ok(())
}

fn on_positive<N>(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: N,
) -> ExpectOpResult<()>
where
    N: IntTrait,
{
    if !received.is_positive() {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is not positive
    received {received}"
            ),
        ));
    }

    Ok(())
}

fn on_negative<N>(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: N,
) -> ExpectOpResult<()>
where
    N: IntTrait,
{
    if !received.is_negative() {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is not negative
    received {received}"
            ),
        ));
    }

    Ok(())
}

fn on_comparison<F>(
    parent: &ExpectInteger,
    context: &mut Context<'_>,
    received: JsonInteger,
    expected: JsonInteger,
    comparison: F,
    comparison_name: &'static str,
) -> ExpectOpResult<()>
where
    F: Fn(JsonInteger, JsonInteger) -> bool,
{
    if !comparison(received, expected) {
        return Err(ExpectOpError::custom(
            parent,
            context,
            format!(
                "integer is out of bounds,
    expected {comparison_name} {expected}
    received {received}"
            ),
        ));
    }

    Ok(())
}

trait IntTrait: Copy + Display + Debug {
    fn is_positive(&self) -> bool;
    fn is_negative(&self) -> bool;
}

impl IntTrait for i64 {
    #[inline]
    fn is_positive(&self) -> bool {
        *self > 0
    }

    #[inline]
    fn is_negative(&self) -> bool {
        *self < 0
    }
}

impl IntTrait for u64 {
    #[inline]
    fn is_positive(&self) -> bool {
        true
    }

    #[inline]
    fn is_negative(&self) -> bool {
        false
    }
}
