//! Property-based fuzz tests for the evaluation engine.
//!
//! These use proptest to generate random inputs and verify invariants:
//! - The engine never panics on any input
//! - Algebraic properties (commutativity, identities) hold
//! - Division by zero always produces errors

use calcpad_engine::context::EvalContext;
use calcpad_engine::pipeline::eval_line;
use calcpad_engine::types::ResultType;
use proptest::prelude::*;

fn arb_small_int() -> impl Strategy<Value = i64> {
    -10000i64..10000
}

fn arb_operator() -> impl Strategy<Value = &'static str> {
    prop::sample::select(vec!["+", "-", "*"])
}

// =========================================================================
// No-panic guarantees: valid expressions
// =========================================================================

proptest! {
    #[test]
    fn valid_number_never_panics(n in arb_small_int()) {
        let input = format!("{}", n);
        let mut ctx = EvalContext::new();
        let _result = eval_line(&input, &mut ctx);
    }

    #[test]
    fn valid_binary_expr_never_panics(
        a in arb_small_int(),
        b in arb_small_int(),
        op in arb_operator()
    ) {
        let input = format!("{} {} {}", a, op, b);
        let mut ctx = EvalContext::new();
        let _result = eval_line(&input, &mut ctx);
    }

    #[test]
    fn valid_complex_expr_never_panics(
        a in arb_small_int(),
        b in 1i64..1000,
        c in arb_small_int(),
        op1 in arb_operator(),
        op2 in arb_operator()
    ) {
        let input = format!("({} {} {}) {} {}", a, op1, b, op2, c);
        let mut ctx = EvalContext::new();
        let _result = eval_line(&input, &mut ctx);
    }
}

// =========================================================================
// No-panic guarantees: malformed / garbage input
// =========================================================================

proptest! {
    #[test]
    fn malformed_garbage_never_panics(
        s in "[a-zA-Z0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?]{1,50}"
    ) {
        let mut ctx = EvalContext::new();
        let _result = eval_line(&s, &mut ctx);
    }

    #[test]
    fn malformed_unmatched_parens_never_panics(
        s in "\\({0,5}[0-9]{1,4}[+\\-*/]{0,2}[0-9]{0,4}\\){0,5}"
    ) {
        let mut ctx = EvalContext::new();
        let _result = eval_line(&s, &mut ctx);
    }

    #[test]
    fn empty_and_whitespace_never_panics(s in "\\s{0,20}") {
        let mut ctx = EvalContext::new();
        let _result = eval_line(&s, &mut ctx);
    }

    #[test]
    fn random_identifiers_never_panics(s in "[a-z]{1,10}") {
        let mut ctx = EvalContext::new();
        let _result = eval_line(&s, &mut ctx);
    }
}

// =========================================================================
// Algebraic properties
// =========================================================================

proptest! {
    #[test]
    fn addition_commutativity(a in arb_small_int(), b in arb_small_int()) {
        let mut ctx1 = EvalContext::new();
        let mut ctx2 = EvalContext::new();
        let r1 = eval_line(&format!("{} + {}", a, b), &mut ctx1);
        let r2 = eval_line(&format!("{} + {}", b, a), &mut ctx2);

        match (r1.result_type(), r2.result_type()) {
            (ResultType::Number, ResultType::Number) => {
                let v1 = r1.metadata.raw_value.unwrap();
                let v2 = r2.metadata.raw_value.unwrap();
                prop_assert!((v1 - v2).abs() < 1e-10,
                    "{} + {} = {} but {} + {} = {}", a, b, v1, b, a, v2);
            }
            _ => {
                prop_assert_eq!(r1.result_type(), r2.result_type());
            }
        }
    }

    #[test]
    fn multiplication_commutativity(a in arb_small_int(), b in arb_small_int()) {
        let mut ctx1 = EvalContext::new();
        let mut ctx2 = EvalContext::new();
        let r1 = eval_line(&format!("{} * {}", a, b), &mut ctx1);
        let r2 = eval_line(&format!("{} * {}", b, a), &mut ctx2);

        match (r1.result_type(), r2.result_type()) {
            (ResultType::Number, ResultType::Number) => {
                let v1 = r1.metadata.raw_value.unwrap();
                let v2 = r2.metadata.raw_value.unwrap();
                prop_assert!((v1 - v2).abs() < 1e-6,
                    "{} * {} = {} but {} * {} = {}", a, b, v1, b, a, v2);
            }
            _ => {
                prop_assert_eq!(r1.result_type(), r2.result_type());
            }
        }
    }
}

// =========================================================================
// Division by zero
// =========================================================================

proptest! {
    #[test]
    fn division_by_zero_always_error(a in arb_small_int()) {
        let mut ctx = EvalContext::new();
        let r = eval_line(&format!("{} / 0", a), &mut ctx);
        prop_assert_eq!(r.result_type(), ResultType::Error);
    }
}

// =========================================================================
// Identity operations
// =========================================================================

proptest! {
    #[test]
    fn add_zero_identity(a in arb_small_int()) {
        let mut ctx = EvalContext::new();
        let r = eval_line(&format!("{} + 0", a), &mut ctx);
        if r.result_type() == ResultType::Number {
            let v = r.metadata.raw_value.unwrap();
            prop_assert!((v - a as f64).abs() < 1e-10,
                "{} + 0 should be {} but got {}", a, a, v);
        }
    }

    #[test]
    fn mul_one_identity(a in arb_small_int()) {
        let mut ctx = EvalContext::new();
        let r = eval_line(&format!("{} * 1", a), &mut ctx);
        if r.result_type() == ResultType::Number {
            let v = r.metadata.raw_value.unwrap();
            prop_assert!((v - a as f64).abs() < 1e-10,
                "{} * 1 should be {} but got {}", a, a, v);
        }
    }
}
