use crate::errors::PolicyEngineError;
use serde_json::Number;

pub fn compare_numeric_equality_with_epsilon_tolerance(
    en: &Number,
    an: &Number,
) -> Result<bool, PolicyEngineError> {
    let af = an
        .as_f64()
        .ok_or(PolicyEngineError::FloatCoercionError { value: an.clone() })?;

    let ef = en
        .as_f64()
        .ok_or(PolicyEngineError::FloatCoercionError { value: en.clone() })?;

    Ok((ef - af).abs() < f64::EPSILON)
}

#[cfg(test)]
mod tests {
    use super::compare_numeric_equality_with_epsilon_tolerance;
    use serde_json::{Number, json};

    fn n(v: serde_json::Value) -> Number {
        v.as_number().expect("value should be a number").clone()
    }

    #[test]
    fn equal_numbers_true() {
        let expected = n(json!(10));
        let actual = n(json!(10));

        let res = compare_numeric_equality_with_epsilon_tolerance(&expected, &actual).expect("ok");
        assert!(res);
    }

    #[test]
    fn within_epsilon_true() {
        // Difference smaller than EPSILON should be considered equal.
        let expected_f = 1.0_f64;
        let actual_f = expected_f + (f64::EPSILON / 2.0);

        let expected = Number::from_f64(expected_f).expect("finite");
        let actual = Number::from_f64(actual_f).expect("finite");

        let res = compare_numeric_equality_with_epsilon_tolerance(&expected, &actual).expect("ok");
        assert!(res);
    }

    #[test]
    fn outside_epsilon_false() {
        // Difference larger than EPSILON should be considered not equal.
        let expected_f = 1.0_f64;
        let actual_f = expected_f + (f64::EPSILON * 2.0);

        let expected = Number::from_f64(expected_f).expect("finite");
        let actual = Number::from_f64(actual_f).expect("finite");

        let res = compare_numeric_equality_with_epsilon_tolerance(&expected, &actual).expect("ok");
        assert!(!res);
    }

    #[test]
    fn cannot_construct_non_finite_numbers() {
        // serde_json::Number cannot represent NaN/Inf; from_f64 returns None.
        assert!(Number::from_f64(f64::NAN).is_none());
        assert!(Number::from_f64(f64::INFINITY).is_none());
    }
}
