use crate::errors::PolicyEngineError;
use crate::match_utils::compare_numeric_with_coercion::compare_numeric_with_coercion;
use crate::match_utils::compare_value_equality_with_type_coercion::compare_value_equality_with_type_coercion;
use crate::match_utils::expected_includes_value_matching_actual::expected_includes_value_matching_actual;
use crate::policy_types::{PropertyScalarMatcher, ValueScalarOperator};
use crate::regex_matcher::RegexMatcher;
use serde_json::Value;

pub fn property_scalar_matcher_matches(
    condition: &PropertyScalarMatcher,
    actual_value: &Value,
    expected_value: &Value,
    regex_matcher: &RegexMatcher,
) -> Result<bool, PolicyEngineError> {
    match (&condition.op, actual_value) {
        // Null-compatible
        (ValueScalarOperator::Eq, _) => {
            compare_value_equality_with_type_coercion(expected_value, actual_value)
        }
        (ValueScalarOperator::Ne, _) => {
            compare_value_equality_with_type_coercion(expected_value, actual_value).map(|b| !b)
        }
        (ValueScalarOperator::In, _) => {
            expected_includes_value_matching_actual(expected_value, actual_value)
        }
        // Throw appropriate errors if actual is null
        (ValueScalarOperator::Regex, Value::Null) => Err(PolicyEngineError::ComparisonError {
            required_type: "string".into(),
            source_type: "null".into(),
        }),
        // Non-null compatible numeric comparisons.
        // Note: null handling (including error reporting for null actual/expected values)
        // is encapsulated within `compare_numeric_with_coercion`.
        (ValueScalarOperator::Lt, _) => {
            compare_numeric_with_coercion(expected_value, actual_value, |a, b| a < b)
        }
        (ValueScalarOperator::Gt, _) => {
            compare_numeric_with_coercion(expected_value, actual_value, |a, b| a > b)
        }
        (ValueScalarOperator::Lte, _) => {
            compare_numeric_with_coercion(expected_value, actual_value, |a, b| a <= b)
        }
        (ValueScalarOperator::Gte, _) => {
            compare_numeric_with_coercion(expected_value, actual_value, |a, b| a >= b)
        }
        (ValueScalarOperator::Regex, _) => {
            regex_matcher.regex_matches(expected_value, actual_value)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::property_scalar_matcher_matches;
    use crate::policy_types::{PropertyScalarMatcher, ValueOrReference, ValueScalarOperator};
    use crate::regex_matcher::RegexMatcher;
    use serde_json::{Value, json};

    fn matcher(op: ValueScalarOperator) -> PropertyScalarMatcher {
        PropertyScalarMatcher {
            key: "x".to_string(),
            op,
            value: ValueOrReference::Value(json!(null)), // not used by the function under test
        }
    }

    #[test]
    fn eq_and_ne_matchers() {
        let rx = RegexMatcher::default();
        // Eq numbers
        let m = matcher(ValueScalarOperator::Eq);
        assert!(property_scalar_matcher_matches(&m, &json!(10), &json!(10), &rx).unwrap());
        // Eq mixed int/float
        assert!(property_scalar_matcher_matches(&m, &json!(10.0), &json!(10), &rx).unwrap());

        // Ne strings
        let m_ne = matcher(ValueScalarOperator::Ne);
        assert!(property_scalar_matcher_matches(&m_ne, &json!("a"), &json!("b"), &rx).unwrap());
        // Ne equal should be false
        assert!(!property_scalar_matcher_matches(&m_ne, &json!(5), &json!(5), &rx).unwrap());
    }

    #[test]
    fn in_operator_matches() {
        let rx = RegexMatcher::default();
        let m = matcher(ValueScalarOperator::In);
        assert!(
            property_scalar_matcher_matches(&m, &json!("x"), &json!(["a", "x", "b"]), &rx).unwrap()
        );
        assert!(!property_scalar_matcher_matches(&m, &json!(2), &json!([0, 1, 3]), &rx).unwrap());
    }

    #[test]
    fn numeric_comparisons() {
        let rx = RegexMatcher::default();
        // Lt
        assert!(
            property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Lt),
                &json!(3),
                &json!(5),
                &rx
            )
            .unwrap()
        );
        assert!(
            !property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Lt),
                &json!(7),
                &json!(5),
                &rx
            )
            .unwrap()
        );
        // Gt
        assert!(
            property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Gt),
                &json!(7),
                &json!(5),
                &rx
            )
            .unwrap()
        );
        // Lte
        assert!(
            property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Lte),
                &json!(5),
                &json!(5),
                &rx
            )
            .unwrap()
        );
        // Gte
        assert!(
            property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Gte),
                &json!(5),
                &json!(5),
                &rx
            )
            .unwrap()
        );
    }

    #[test]
    fn numeric_ops_error_on_null_actual() {
        let rx = RegexMatcher::default();
        for op in [
            ValueScalarOperator::Lt,
            ValueScalarOperator::Gt,
            ValueScalarOperator::Lte,
            ValueScalarOperator::Gte,
        ] {
            let err = property_scalar_matcher_matches(&matcher(op), &Value::Null, &json!(1), &rx)
                .unwrap_err();
            match err {
                crate::errors::PolicyEngineError::ComparisonError {
                    required_type,
                    source_type,
                } => {
                    assert_eq!(required_type, "number");
                    assert_eq!(source_type, "null");
                }
                _ => panic!("expected ComparisonError"),
            }
        }
    }

    #[test]
    fn regex_matches_and_errors() {
        let rx = RegexMatcher::default();

        // Happy path match
        assert!(
            property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Regex),
                &json!("hello world"),
                &json!("^hello"),
                &rx
            )
            .unwrap()
        );
        // No match
        assert!(
            !property_scalar_matcher_matches(
                &matcher(ValueScalarOperator::Regex),
                &json!("world"),
                &json!("^hello"),
                &rx
            )
            .unwrap()
        );

        // Error: actual is null for regex
        let err = property_scalar_matcher_matches(
            &matcher(ValueScalarOperator::Regex),
            &Value::Null,
            &json!(".*"),
            &rx,
        )
        .unwrap_err();
        match err {
            crate::errors::PolicyEngineError::ComparisonError {
                required_type,
                source_type,
            } => {
                assert_eq!(required_type, "string");
                assert_eq!(source_type, "null");
            }
            _ => panic!("expected ComparisonError for regex with null actual"),
        }

        // Error: expected is non-string for regex
        let err = property_scalar_matcher_matches(
            &matcher(ValueScalarOperator::Regex),
            &json!("abc"),
            &json!(123),
            &rx,
        )
        .unwrap_err();
        match err {
            crate::errors::PolicyEngineError::ComparisonError {
                required_type,
                source_type,
            } => {
                assert_eq!(required_type, "string");
                assert_eq!(source_type, "number");
            }
            _ => panic!("expected ComparisonError for non-string expected"),
        }

        // Error: invalid regex pattern
        let err = property_scalar_matcher_matches(
            &matcher(ValueScalarOperator::Regex),
            &json!("abc"),
            &json!("[unclosed"),
            &rx,
        )
        .unwrap_err();
        match err {
            crate::errors::PolicyEngineError::InvalidRegexError { value } => {
                assert_eq!(value, "[unclosed");
            }
            _ => panic!("expected InvalidRegexError for invalid regex pattern"),
        }
    }
}
