//! Robustness tests for the ReconciliationEngine
//! Tests high contention scenarios, cycle detection, and edge cases.

use cannon_core::types::{NormalizedEntity, Decision};
use cannon_core::overrides::OverrideResolver;
use cannon_core::ReconciliationEngine;
use cannon_common::ir::{IdentityPlan, CompiledBlocking, CompiledRule, MatchGraph, SurvivorshipPolicy, DecisionConfig, CompiledRuleType, CompiledRelation, BlockingKey, BlockingTransformation, ScoringMethod};
use cannon_common::spec::ConflictStrategy;
use uuid::Uuid;
use std::collections::HashMap;

/// Helper to create a test entity
fn make_entity(id: Uuid, name: &str, email: &str) -> NormalizedEntity {
    let mut data = HashMap::new();
    data.insert("name".to_string(), name.to_string());
    data.insert("email".to_string(), email.to_string());
    
    NormalizedEntity {
        id,
        tenant_id: Uuid::nil(),
        external_id: id.to_string(),
        entity_type: "customer".to_string(),
        data,
        source_name: "test".to_string(),
        valid_from: None,
        valid_to: None,
        last_updated: chrono::Utc::now(),
    }
}

/// Create a basic identity plan for testing
fn make_basic_plan() -> IdentityPlan {
    IdentityPlan {
        entity: "customer".to_string(),
        plan_hash: "test-hash".to_string(),
        identity_version: "v1".to_string(),
        sources: vec![],
        blocking: CompiledBlocking {
            strategy: cannon_common::spec::BlockingStrategy::Single,
            keys: vec![BlockingKey {
                fields: vec!["name".to_string()],
                transformations: vec![BlockingTransformation::Lowercase],
            }],
            fallback_sample_rate: None,
            lsh_bands: None,
            lsh_rows: None,
            neighborhood_window: None,
            sort_fields: vec![],
        },
        match_graph: MatchGraph {
            rules: vec![CompiledRule {
                name: "name_exact".to_string(),
                rule_type: CompiledRuleType::Exact {
                    field: "name".to_string(),
                    weight: 1.0,
                    case_insensitive: true,
                    normalize: true,
                    normalizer: None,
                },
            }],
            leaf_count: 1,
        },
        survivorship: SurvivorshipPolicy::default(),
        decision: DecisionConfig {
            match_threshold: 0.8,
            review_threshold: Some(0.5),
            reject_threshold: Some(0.2),
            conflict_strategy: ConflictStrategy::PreferHighConfidence,
            review_webhook: None,
            scoring_method: ScoringMethod::WeightedSum,
            ml_ensemble_config: None,
            custom_scoring_config: None,
            fellegi_sunter: None,
            tie_breaking: vec![],
        },
        reference_identifiers: vec![],
        relations: vec![],
        exclusions: vec![],
        compliance: None,
        hierarchy: None,
        audit: None,
        metadata: None,
        governance: None,
    }
}

/// Test HIGH CONTENTION: 1000 entities that all match each other (worst case)
/// This tests stack depth and memory usage in clustering algorithms.
#[test]
fn test_high_contention_large_cluster() {
    let plan = make_basic_plan();
    let engine = ReconciliationEngine::from_plan(&plan);

    // Create 1000 entities with the same name (they should all cluster together)
    let entities: Vec<NormalizedEntity> = (0..1000)
        .map(|i| {
            make_entity(Uuid::new_v4(), "John Doe", &format!("john{}@example.com", i))
        })
        .collect();

    let resolver = OverrideResolver::new(vec![]);

    // This should NOT stack overflow or OOM
    let decisions = engine.reconcile(&entities, &resolver);
    
    // Should produce decisions (blocking will group by name "john doe")
    assert!(decisions.len() > 0, "Should produce some decisions");
    
    // Verify clustering completes without panic
    let clusters = engine.cluster_decisions(&entities, &decisions);
    
    // All 1000 entities should be in a single cluster
    assert!(clusters.iter().any(|c| c.len() >= 100), "Should have at least one large cluster");
}

/// Test MODERATE CONTENTION: Multiple distinct clusters
#[test]
fn test_moderate_contention_multiple_clusters() {
    let plan = make_basic_plan();
    let engine = ReconciliationEngine::from_plan(&plan);

    // Create 100 entities across 10 different names (10 clusters of 10)
    let mut entities = Vec::new();
    for cluster_idx in 0..10 {
        let name = format!("Person{}", cluster_idx);
        for entity_idx in 0..10 {
            entities.push(make_entity(
                Uuid::new_v4(),
                &name,
                &format!("{}{}@example.com", name.to_lowercase(), entity_idx),
            ));
        }
    }

    let resolver = OverrideResolver::new(vec![]);
    let decisions = engine.reconcile(&entities, &resolver);
    let clusters = engine.cluster_decisions(&entities, &decisions);

    // Should have approximately 10 clusters
    assert!(clusters.len() >= 5 && clusters.len() <= 15, "Expected ~10 clusters, got {}", clusters.len());
    
    // Each cluster should have ~10 members
    for cluster in &clusters {
        assert!(cluster.len() >= 5, "Each cluster should have multiple members");
    }
}

/// Test CYCLE DETECTION: Ensure relation propagation doesn't infinite loop on circular graphs
/// A -> B, B -> C, C -> A should not cause infinite recursion
#[test]
fn test_cycle_detection_in_relations() {
    let mut plan = make_basic_plan();
    
    // Add relations that could form cycles
    plan.relations = vec![
        CompiledRelation { 
            name: "parent".to_string(),
            from_entity: "customer".to_string(), 
            to_entity: "customer".to_string(), 
            join_key: "parent_id".to_string(),
            cardinality: cannon_common::spec::Cardinality::ManyToOne,
            propagate_match: true,
        },
    ];

    let engine = ReconciliationEngine::from_plan(&plan);

    // Create entities with circular relation references
    let id_a = Uuid::new_v4();
    let id_b = Uuid::new_v4();
    let id_c = Uuid::new_v4();

    let mut data_a = HashMap::new();
    data_a.insert("name".to_string(), "Alice".to_string());
    data_a.insert("parent_id".to_string(), id_b.to_string());
    data_a.insert("id".to_string(), id_a.to_string());

    let mut data_b = HashMap::new();
    data_b.insert("name".to_string(), "Bob".to_string());
    data_b.insert("parent_id".to_string(), id_c.to_string());
    data_b.insert("id".to_string(), id_b.to_string());

    let mut data_c = HashMap::new();
    data_c.insert("name".to_string(), "Charlie".to_string());
    data_c.insert("parent_id".to_string(), id_a.to_string()); // Cycle back to A
    data_c.insert("id".to_string(), id_c.to_string());

    let entities = vec![
        NormalizedEntity { id: id_a, tenant_id: Uuid::nil(), external_id: "a".to_string(), entity_type: "customer".to_string(), data: data_a, source_name: "test".to_string(), valid_from: None, valid_to: None, last_updated: chrono::Utc::now() },
        NormalizedEntity { id: id_b, tenant_id: Uuid::nil(), external_id: "b".to_string(), entity_type: "customer".to_string(), data: data_b, source_name: "test".to_string(), valid_from: None, valid_to: None, last_updated: chrono::Utc::now() },
        NormalizedEntity { id: id_c, tenant_id: Uuid::nil(), external_id: "c".to_string(), entity_type: "customer".to_string(), data: data_c, source_name: "test".to_string(), valid_from: None, valid_to: None, last_updated: chrono::Utc::now() },
    ];

    let resolver = OverrideResolver::new(vec![]);
    let initial_decisions = engine.reconcile(&entities, &resolver);
    let clusters = engine.cluster_decisions(&entities, &initial_decisions);

    // This should NOT infinite loop - it should complete within reasonable time
    let propagated = engine.propagate_relations(&clusters, &entities, &resolver, &initial_decisions);

    // We just verify it completes without hanging
    assert!(propagated.len() >= 0, "Propagation should complete without infinite loop");
}

/// Test EMPTY INPUT: Engine should handle edge cases gracefully
#[test]
fn test_empty_input() {
    let plan = make_basic_plan();
    let engine = ReconciliationEngine::from_plan(&plan);

    let entities: Vec<NormalizedEntity> = vec![];
    let resolver = OverrideResolver::new(vec![]);

    let decisions = engine.reconcile(&entities, &resolver);
    assert!(decisions.is_empty(), "Empty input should produce no decisions");

    let clusters = engine.cluster_decisions(&entities, &decisions);
    assert!(clusters.is_empty(), "Empty input should produce no clusters");
}

/// Test SINGLE ENTITY: No pairs to compare
#[test]
fn test_single_entity() {
    let plan = make_basic_plan();
    let engine = ReconciliationEngine::from_plan(&plan);

    let entities = vec![make_entity(Uuid::new_v4(), "Solo", "solo@example.com")];
    let resolver = OverrideResolver::new(vec![]);

    let decisions = engine.reconcile(&entities, &resolver);
    // A single entity has no pairs to compare
    assert!(decisions.is_empty(), "Single entity should produce no decisions");

    let clusters = engine.cluster_decisions(&entities, &decisions);
    // Should have 1 singleton cluster
    assert_eq!(clusters.len(), 1, "Single entity should form one cluster");
    assert_eq!(clusters[0].len(), 1, "Singleton cluster should have 1 member");
}
