//! Property tests for override merge engine

use proptest::prelude::*;

use crate::{
    resolve::merge::{
        apply_override,
        is_idempotent,
    },
    syntax::ast::*,
};

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

fn valid_ident() -> impl Strategy<Value = String> {
    "[a-zA-Z_][a-zA-Z0-9_]{0,15}"
}

fn valid_field() -> impl Strategy<Value = Field> {
    (valid_ident(), 0i64..1000).prop_map(|(name, value)| Field {
        name,
        value: Value::Number(value),
        span: Span::new(0, 10),
    })
}

fn valid_field_list() -> impl Strategy<Value = Vec<Field>> {
    prop::collection::vec(valid_field(), 0..10)
        // Ensure unique field names
        .prop_map(|fields| {
            let mut unique_fields = Vec::new();
            let mut seen_names = std::collections::HashSet::new();

            for field in fields {
                if seen_names.insert(field.name.clone()) {
                    unique_fields.push(field);
                }
            }
            unique_fields
        })
}

fn valid_set_op() -> impl Strategy<Value = OverrideOp> {
    valid_field().prop_map(OverrideOp::Set)
}

fn valid_remove_op() -> impl Strategy<Value = OverrideOp> {
    valid_ident().prop_map(OverrideOp::Remove)
}

fn valid_append_op() -> impl Strategy<Value = OverrideOp> {
    valid_field().prop_map(OverrideOp::Append)
}

fn valid_override_ops() -> impl Strategy<Value = Vec<OverrideOp>> {
    prop::collection::vec(
        prop_oneof![valid_set_op(), valid_remove_op(), valid_append_op(),],
        0..10,
    )
}

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

proptest! {
    #[test]
    fn test_empty_override_returns_base(base in valid_field_list()) {
        let ops: Vec<OverrideOp> = vec![];
        let result = apply_override(base.clone(), &ops).unwrap();

        assert_eq!(result, base);
    }

    #[test]
    fn test_override_never_panics(
        base in valid_field_list(),
        ops in valid_override_ops()
    ) {
        // Should never panic, might return error
        let _ = apply_override(base, &ops);
    }

    #[test]
    fn test_set_always_succeeds(
        base in valid_field_list(),
        field in valid_field()
    ) {
        let ops = vec![OverrideOp::Set(field)];
        let result = apply_override(base, &ops);

        assert!(result.is_ok(), "Set operations should always succeed");
    }

    #[test]
    fn test_remove_always_succeeds(
        base in valid_field_list(),
        name in valid_ident()
    ) {
        let ops = vec![OverrideOp::Remove(name)];
        let result = apply_override(base, &ops);

        assert!(result.is_ok(), "Remove operations should always succeed");
    }

    #[test]
    fn test_set_adds_or_replaces(
        base in valid_field_list(),
        field in valid_field()
    ) {
        let ops = vec![OverrideOp::Set(field.clone())];
        let result = apply_override(base.clone(), &ops).unwrap();

        // Result should contain the field
        assert!(result.iter().any(|f| f.name == field.name));

        // If field was in base, length should be same
        // If field was not in base, length should increase by 1
        let was_in_base = base.iter().any(|f| f.name == field.name);
        if was_in_base {
            assert_eq!(result.len(), base.len());
        } else {
            assert_eq!(result.len(), base.len() + 1);
        }
    }

    #[test]
    fn test_remove_decreases_or_maintains_length(
        base in valid_field_list(),
        name in valid_ident()
    ) {
        let ops = vec![OverrideOp::Remove(name.clone())];
        let result = apply_override(base.clone(), &ops).unwrap();

        // Result should not contain the field
        assert!(!result.iter().any(|f| f.name == name));

        // Length should be <= base length
        assert!(result.len() <= base.len());
    }

    #[test]
    fn test_append_to_empty_succeeds(field in valid_field()) {
        let base = vec![];
        let ops = vec![OverrideOp::Append(field.clone())];
        let result = apply_override(base, &ops).unwrap();

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].name, field.name);
    }

    #[test]
    fn test_append_duplicate_fails(
        base in valid_field_list().prop_filter("non-empty", |f| !f.is_empty())
    ) {
        // Try to append a field that already exists
        let existing_field = base[0].clone();
        let ops = vec![OverrideOp::Append(existing_field)];
        let result = apply_override(base, &ops);

        assert!(result.is_err(), "Appending duplicate field should fail");
    }

    #[test]
    fn test_set_is_idempotent_property(
        base in valid_field_list(),
        field in valid_field()
    ) {
        let ops = vec![OverrideOp::Set(field)];
        assert!(is_idempotent(&base, &ops));
    }

    #[test]
    fn test_remove_is_idempotent_property(
        base in valid_field_list(),
        name in valid_ident()
    ) {
        let ops = vec![OverrideOp::Remove(name)];
        assert!(is_idempotent(&base, &ops));
    }

    #[test]
    fn test_multiple_sets_last_wins(
        base in valid_field_list(),
        name in valid_ident(),
        val1 in 0i64..100,
        val2 in 100i64..200
    ) {
        let field1 = Field {
            name: name.clone(),
            value: Value::Number(val1),
            span: Span::new(0, 10),
        };
        let field2 = Field {
            name: name.clone(),
            value: Value::Number(val2),
            span: Span::new(0, 10),
        };

        let ops = vec![
            OverrideOp::Set(field1),
            OverrideOp::Set(field2.clone()),
        ];

        let result = apply_override(base, &ops).unwrap();
        let final_field = result.iter().find(|f| f.name == name).unwrap();

        // Last set should win
        assert_eq!(final_field.value, field2.value);
    }

    #[test]
    fn test_set_after_remove_adds_back(
        base in valid_field_list().prop_filter("non-empty", |f| !f.is_empty())
    ) {
        let field = base[0].clone();
        let name = field.name.clone();

        let ops = vec![
            OverrideOp::Remove(name.clone()),
            OverrideOp::Set(field.clone()),
        ];

        let result = apply_override(base, &ops).unwrap();

        // Field should exist after remove + set
        assert!(result.iter().any(|f| f.name == name));
    }

    #[test]
    fn test_remove_after_set_cancels_out(
        base in valid_field_list(),
        field in valid_field()
    ) {
        let name = field.name.clone();
        let ops = vec![
            OverrideOp::Set(field),
            OverrideOp::Remove(name.clone()),
        ];

        let result = apply_override(base, &ops).unwrap();

        // Field should not exist after set + remove
        assert!(!result.iter().any(|f| f.name == name));
    }

    #[test]
    fn test_operation_order_matters(
        base in valid_field_list(),
        name in valid_ident(),
        val1 in 0i64..100,
        val2 in 100i64..200
    ) {
        let field1 = Field {
            name: name.clone(),
            value: Value::Number(val1),
            span: Span::new(0, 10),
        };
        let field2 = Field {
            name: name.clone(),
            value: Value::Number(val2),
            span: Span::new(0, 10),
        };

        let ops1 = vec![
            OverrideOp::Set(field1.clone()),
            OverrideOp::Set(field2.clone()),
        ];

        let ops2 = vec![
            OverrideOp::Set(field2.clone()),
            OverrideOp::Set(field1.clone()),
        ];

        let result1 = apply_override(base.clone(), &ops1).unwrap();
        let result2 = apply_override(base, &ops2).unwrap();

        // Different order should give different results (last set wins)
        let value1 = result1.iter().find(|f| f.name == name).unwrap().value.clone();
        let value2 = result2.iter().find(|f| f.name == name).unwrap().value.clone();

        assert_eq!(value1, Value::Number(val2));
        assert_eq!(value2, Value::Number(val1));
    }

    #[test]
    fn test_append_after_remove_succeeds(
        base in valid_field_list().prop_filter("non-empty", |f| !f.is_empty())
    ) {
        let field = base[0].clone();
        let name = field.name.clone();

        let ops = vec![
            OverrideOp::Remove(name.clone()),
            OverrideOp::Append(field.clone()),
        ];

        let result = apply_override(base, &ops);

        // Should succeed - field was removed then appended
        assert!(result.is_ok());
        let fields = result.unwrap();
        assert!(fields.iter().any(|f| f.name == name));
    }

    #[test]
    fn test_associativity_for_sets(
        base in valid_field_list(),
        f1 in valid_field(),
        f2 in valid_field(),
        f3 in valid_field()
    ) {
        // Skip if any fields have the same name
        if f1.name == f2.name || f2.name == f3.name || f1.name == f3.name {
            return Ok(());
        }

        // (f1, f2), f3
        let ops1 = vec![
            OverrideOp::Set(f1.clone()),
            OverrideOp::Set(f2.clone()),
        ];
        let intermediate = apply_override(base.clone(), &ops1).unwrap();
        let result1 = apply_override(intermediate, &[OverrideOp::Set(f3.clone())]).unwrap();

        // f1, (f2, f3)
        let ops2 = vec![
            OverrideOp::Set(f2.clone()),
            OverrideOp::Set(f3.clone()),
        ];
        let intermediate = apply_override(base.clone(), &ops2).unwrap();
        let result2 = apply_override(intermediate, &[OverrideOp::Set(f1.clone())]).unwrap();

        // Results might differ due to field order, but should have same fields
        assert_eq!(result1.len(), result2.len());
        for field in &result1 {
            assert!(result2.iter().any(|f| f.name == field.name));
        }
    }
}
