//! Property tests for semantic validation

use std::collections::HashSet;

use proptest::prelude::*;

use crate::{
    resolve::{
        validate::*,
        ErrorCollector,
    },
    syntax::ast::*,
};

// ===== Generators =====

fn valid_bond_field() -> impl Strategy<Value = Field> {
    (0.0..=1.0).prop_map(|f| Field {
        name: "bond".to_string(),
        value: Value::Decimal(f),
        span: Span::new(0, 10),
    })
}

fn invalid_bond_field() -> impl Strategy<Value = Field> {
    prop_oneof![
        (-100.0..0.0).prop_map(|f| Field {
            name: "bond".to_string(),
            value: Value::Decimal(f),
            span: Span::new(0, 10),
        }),
        (1.0..100.0).prop_map(|f| Field {
            name: "bond".to_string(),
            value: Value::Decimal(f),
            span: Span::new(0, 10),
        }),
    ]
}

fn valid_age_field() -> impl Strategy<Value = Field> {
    (0i64..=150).prop_map(|age| Field {
        name: "age".to_string(),
        value: Value::Number(age),
        span: Span::new(0, 10),
    })
}

fn invalid_age_field() -> impl Strategy<Value = Field> {
    prop_oneof![
        (-100i64..-1).prop_map(|age| Field {
            name: "age".to_string(),
            value: Value::Number(age),
            span: Span::new(0, 10),
        }),
        (151i64..300).prop_map(|age| Field {
            name: "age".to_string(),
            value: Value::Number(age),
            span: Span::new(0, 10),
        }),
    ]
}

// ===== Property Tests =====

proptest! {
    #[test]
    fn test_valid_bond_always_passes(field in valid_bond_field()) {
        let fields = vec![field];
        let mut collector = ErrorCollector::new();
        validate_trait_ranges(&fields, &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_invalid_bond_always_fails(field in invalid_bond_field()) {
        let fields = vec![field];
        let mut collector = ErrorCollector::new();
        validate_trait_ranges(&fields, &mut collector);
        assert!(collector.has_errors());
    }

    #[test]
    fn test_valid_age_always_passes(field in valid_age_field()) {
        let fields = vec![field];
        let mut collector = ErrorCollector::new();
        validate_trait_ranges(&fields, &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_invalid_age_always_fails(field in invalid_age_field()) {
        let fields = vec![field];
        let mut collector = ErrorCollector::new();
        validate_trait_ranges(&fields, &mut collector);
        assert!(collector.has_errors());
    }

    #[test]
    fn test_bond_exact_bounds(f in 0.0f64..=1.0) {
        let field = Field {
            name: "bond".to_string(),
            value: Value::Decimal(f),
            span: Span::new(0, 10),
        };
        let mut collector = ErrorCollector::new();
        validate_trait_ranges(&[field], &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_relationship_bond_in_range_passes(
        bond_value in 0.0f64..=1.0
    ) {
        let rel = Relationship {
            name: "Test".to_string(),
            participants: vec![],
            fields: vec![Field {
                name: "bond".to_string(),
                value: Value::Decimal(bond_value),
                span: Span::new(0, 10),
            }],
            span: Span::new(0, 100),
        };

        let mut collector = ErrorCollector::new();
        validate_relationship_bonds(&[rel], &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_relationship_bond_out_of_range_fails(
        bond_value in prop_oneof![(-100.0..0.0), (1.0..100.0)]
    ) {
        let rel = Relationship {
            name: "Test".to_string(),
            participants: vec![],
            fields: vec![Field {
                name: "bond".to_string(),
                value: Value::Decimal(bond_value),
                span: Span::new(0, 10),
            }],
            span: Span::new(0, 100),
        };

        let mut collector = ErrorCollector::new();
        validate_relationship_bonds(&[rel], &mut collector);
        assert!(collector.has_errors());
    }

    #[test]
    fn test_life_arc_with_valid_transitions_passes(
        state1_name in "[a-z]{3,10}",
        state2_name in "[a-z]{3,10}"
    ) {
        if state1_name == state2_name {
            return Ok(());
        }

        let life_arc = LifeArc {
            name: "Test".to_string(),
            required_fields: vec![],
            states: vec![
                ArcState {
                    name: state1_name.clone(),
                    on_enter: None,
                    transitions: vec![Transition {
                        to: state2_name.clone(),
                        condition: Expr::BooleanLit(true),
                        span: Span::new(0, 10),
                    }],
                    span: Span::new(0, 50),
                },
                ArcState {
                    name: state2_name,
                    on_enter: None,
                    transitions: vec![],
                    span: Span::new(50, 100),
                },
            ],
            span: Span::new(0, 100),
        };

        let mut collector = ErrorCollector::new();
        validate_life_arc_transitions(&life_arc, &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_known_action_always_passes(
        tree_name in "[a-z]{3,10}",
        action_name in "[a-z]{3,10}"
    ) {
        let mut registry = HashSet::new();
        registry.insert(action_name.clone());

        let tree = Behavior {
            name: tree_name,
            root: BehaviorNode::Action(action_name, vec![]),
            span: Span::new(0, 100),
        };

        let mut collector = ErrorCollector::new();
        validate_behavior_tree_actions(&tree, &registry, &mut collector);
        assert!(!collector.has_errors());
    }

    #[test]
    fn test_unknown_action_always_fails(
        tree_name in "[a-z]{3,10}",
        action_name in "[a-z]{3,10}"
    ) {
        // Create a registry with some actions, but ensure generated action isn't in it
        // Using uppercase letters ensures it won't match the generated lowercase action
        let mut registry = HashSet::new();
        registry.insert("WALK".to_string());
        registry.insert("WORK".to_string());
        registry.insert("EAT".to_string());

        let tree = Behavior {
            name: tree_name,
            root: BehaviorNode::Action(action_name, vec![]),
            span: Span::new(0, 100),
        };

        let mut collector = ErrorCollector::new();
        validate_behavior_tree_actions(&tree, &registry, &mut collector);
        assert!(collector.has_errors());
    }
}
