//! Tests for StackedNER composability with ML backends.
//!
//! These tests verify that StackedNER correctly combines ML backends with
//! pattern and heuristic layers, handles conflicts, and tracks provenance.

#[cfg(feature = "onnx")]
mod onnx_tests {
    use anno::{Model, StackedNER, RegexNER, HeuristicNER};
    use anno::backends::stacked::ConflictStrategy;

    #[test]
    fn test_with_ml_first() {
        // This test requires ONNX feature and a model download
        // Skip in CI if model not available
        if let Ok(gliner) = anno::GLiNEROnnx::new("onnx-community/gliner_small-v2.1") {
            let stacked = StackedNER::with_ml_first(Box::new(gliner));
            
            // Verify layer order
            let layer_names = stacked.layer_names();
            assert!(layer_names[0].contains("gliner"), "ML should be first layer");
            assert!(layer_names.len() >= 3, "Should have ML + Pattern + Heuristic");
            
            // Test extraction
            let text = "Dr. Smith charges $100/hr. Email: smith@test.com";
            let entities = stacked.extract_entities(text, None).unwrap();
            
            // Should find entities from multiple layers
            assert!(!entities.is_empty());
            
            // Verify provenance is set
            for entity in &entities {
                // At least some entities should have provenance
                if entity.provenance.is_some() {
                    assert!(!entity.provenance.as_ref().unwrap().source.is_empty());
                }
            }
        }
    }

    #[test]
    fn test_with_ml_fallback() {
        if let Ok(gliner) = anno::GLiNEROnnx::new("onnx-community/gliner_small-v2.1") {
            let stacked = StackedNER::with_ml_fallback(Box::new(gliner));
            
            // Verify layer order
            let layer_names = stacked.layer_names();
            assert!(layer_names[0].contains("pattern") || layer_names[0].contains("regex"), 
                "Pattern should be first");
            let last_layer = layer_names.last().unwrap();
            assert!(last_layer.contains("gliner"), "ML should be last layer");
            
            // Test extraction
            let text = "Contact alice@example.com about the $500 invoice";
            let entities = stacked.extract_entities(text, None).unwrap();
            
            // Should find structured entities from pattern layer
            let has_email = entities.iter().any(|e| matches!(e.entity_type, anno::EntityType::Email));
            assert!(has_email, "Should find email from pattern layer");
        }
    }

    #[test]
    fn test_custom_ml_stack() {
        if let Ok(gliner) = anno::GLiNEROnnx::new("onnx-community/gliner_small-v2.1") {
            let stacked = StackedNER::builder()
                .layer(RegexNER::new())
                .layer_boxed(Box::new(gliner))
                .layer(HeuristicNER::new())
                .strategy(ConflictStrategy::HighestConf)
                .build();
            
            assert_eq!(stacked.num_layers(), 3);
            
            let text = "Apple Inc. was founded in 1976";
            let entities = stacked.extract_entities(text, None).unwrap();
            
            // Should combine results from all layers
            assert!(!entities.is_empty());
        }
    }

    #[test]
    fn test_multiple_overlapping_entities() {
        // Test the fix for multiple overlap bug
        let stacked = StackedNER::default();
        
        // Create scenario where one entity overlaps with multiple
        // "New York" and "York" both exist, then "New York City" comes in
        let text = "New York City is large";
        let entities = stacked.extract_entities(text, None).unwrap();
        
        // Should not have overlapping entities (unless Union strategy)
        for i in 0..entities.len() {
            for j in (i + 1)..entities.len() {
                let e1 = &entities[i];
                let e2 = &entities[j];
                let overlap = !(e1.end <= e2.start || e2.end <= e1.start);
                // Overlaps are only allowed with Union strategy
                // For default Priority strategy, should not overlap
                if overlap && stacked.strategy() != ConflictStrategy::Union {
                    panic!("Found overlapping entities with {:?} strategy: {:?} and {:?}", 
                        stacked.strategy(), e1, e2);
                }
            }
        }
    }

    #[test]
    #[should_panic(expected = "requires at least one layer")]
    fn test_empty_stack_panics() {
        let _stacked = StackedNER::builder().build();
    }

    #[test]
    fn test_partial_error_handling() {
        // Test that if one layer fails, others still work
        // This is hard to test without a mock, but we can test the structure
        let stacked = StackedNER::default();
        
        // Default stack should work
        let text = "Test $100";
        let result = stacked.extract_entities(text, None);
        assert!(result.is_ok());
        
        // Verify it found something
        if let Ok(entities) = result {
            // Should at least find money
            let has_money = entities.iter().any(|e| matches!(e.entity_type, anno::EntityType::Money));
            assert!(has_money, "Should find money from pattern layer");
        }
    }
}

