# tests/test_algorithm_rules.py
"""Tests for the algorithm selection rule engine."""
import pytest
from pathlib import Path

from fmatch.algorithm_selection.rule_engine import (
    AlgorithmRule,
    AlgorithmSelectionEngine
)


class TestAlgorithmRule:
    """Test individual rule evaluation."""
    
    def test_simple_equality_condition(self):
        """Test simple equality matching."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={"semantic_type": "email"},
            preferred_algorithms=["Exact"]
        )
        
        # Should match
        matches, conf = rule.evaluate({"semantic_type": "email"})
        assert matches == True
        assert conf == 1.0
        
        # Should not match
        matches, conf = rule.evaluate({"semantic_type": "phone"})
        assert matches == False
    
    def test_range_condition(self):
        """Test numeric range conditions."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={
                "avg_length": {"min": 10, "max": 50}
            },
            preferred_algorithms=["WRatio"]
        )
        
        # Should match
        assert rule.evaluate({"avg_length": 25})[0] == True
        assert rule.evaluate({"avg_length": 10})[0] == True
        assert rule.evaluate({"avg_length": 50})[0] == True
        
        # Should not match
        assert rule.evaluate({"avg_length": 9})[0] == False
        assert rule.evaluate({"avg_length": 51})[0] == False
    
    def test_pattern_condition(self):
        """Test regex pattern matching."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={
                "semantic_type": {"pattern": ".*code.*"}
            },
            preferred_algorithms=["Exact"]
        )
        
        # Should match
        assert rule.evaluate({"semantic_type": "product_code"})[0] == True
        assert rule.evaluate({"semantic_type": "postal_code"})[0] == True
        
        # Should not match
        assert rule.evaluate({"semantic_type": "email"})[0] == False
    
    def test_in_list_condition(self):
        """Test 'in' list conditions."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={
                "script": {"in": ["latin", "cyrillic"]}
            },
            preferred_algorithms=["Soundex"]
        )
        
        # Should match
        assert rule.evaluate({"script": "latin"})[0] == True
        assert rule.evaluate({"script": "cyrillic"})[0] == True
        
        # Should not match
        assert rule.evaluate({"script": "arabic"})[0] == False
    
    def test_not_in_list_condition(self):
        """Test 'not_in' list conditions."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={
                "script": {"not_in": ["latin", "unknown"]}
            },
            preferred_algorithms=["JaroWinkler"]
        )
        
        # Should match
        assert rule.evaluate({"script": "arabic"})[0] == True
        assert rule.evaluate({"script": "cjk"})[0] == True
        
        # Should not match
        assert rule.evaluate({"script": "latin"})[0] == False
    
    def test_multiple_conditions(self):
        """Test rules with multiple conditions."""
        rule = AlgorithmRule(
            name="test",
            description="Test rule",
            conditions={
                "semantic_type": "email",
                "avg_length": {"min": 5, "max": 100},
                "encoding_issues": False
            },
            preferred_algorithms=["Exact"]
        )
        
        # All conditions match
        profile = {
            "semantic_type": "email",
            "avg_length": 25,
            "encoding_issues": False
        }
        matches, conf = rule.evaluate(profile)
        assert matches == True
        assert conf == 1.0
        
        # One condition doesn't match
        profile["encoding_issues"] = True
        matches, conf = rule.evaluate(profile)
        assert matches == False
        assert conf == 2/3  # 2 out of 3 conditions match


class TestAlgorithmSelectionEngine:
    """Test the complete selection engine."""
    
    def test_algorithm_scoring(self):
        """Test that algorithm scoring works correctly."""
        # Create a test rule
        rule = AlgorithmRule(
            name="test_exact",
            description="Test",
            conditions={"semantic_type": "product_code"},
            preferred_algorithms=["Exact", "Jaro"],
            weight_modifier=2.0,
            priority=100
        )
        
        # Create engine with just this rule
        engine = AlgorithmSelectionEngine(rules_dir=Path("/nonexistent"))
        engine.rules = [rule]
        
        # Test profile that matches
        profile = {"semantic_type": "product_code"}
        results = engine.select_algorithms(profile, ["Exact", "Jaro", "WRatio", "Soundex"])
        
        # Exact should score highest
        assert results[0][0] == "Exact"
        assert results[0][1] > 0.5
        
        # Jaro should be second
        assert results[1][0] == "Jaro"
        
        # Others should have lower scores
        assert results[2][1] < results[1][1]
    
    def test_priority_ordering(self):
        """Test that higher priority rules take precedence."""
        # Create two conflicting rules
        rule1 = AlgorithmRule(
            name="low_priority",
            description="Low priority rule",
            conditions={"semantic_type": "code"},
            preferred_algorithms=["WRatio"],
            priority=10
        )
        
        rule2 = AlgorithmRule(
            name="high_priority",
            description="High priority rule",
            conditions={"semantic_type": "code"},
            preferred_algorithms=["Exact"],
            priority=100
        )
        
        engine = AlgorithmSelectionEngine(rules_dir=Path("/nonexistent"))
        engine.rules = [rule1, rule2]
        
        # Test
        profile = {"semantic_type": "code"}
        best = engine.get_best_algorithm(profile)
        
        # High priority rule should win
        assert best == "Exact"
    
    def test_no_matching_rules(self):
        """Test behavior when no rules match."""
        engine = AlgorithmSelectionEngine(rules_dir=Path("/nonexistent"))
        engine.rules = []  # No rules
        
        results = engine.select_algorithms({"some": "profile"})
        
        # Should return all algorithms with default scores
        assert len(results) > 0
        assert all(score == 0.5 for _, score in results)


class TestIntegration:
    """Test integration with main engine."""
    
    def test_profile_creation(self):
        """Test creating unified profiles."""
        from fmatch.engine_algorithm_integration import create_column_profile_for_algorithm_selection
        
        column_stats = {
            'unique_ratio': 0.95,
            'avg_length': 25,
            'semantic_type': 'email',
            'has_email_pattern': True
        }
        
        exotic_profile = {
            'script': 'latin',
            'has_emoji': False,
            'encoding_issues': False
        }
        
        profile = create_column_profile_for_algorithm_selection(column_stats, exotic_profile)
        
        assert profile['distinct_ratio'] == 0.95
        assert profile['avg_length'] == 25
        assert profile['semantic_type'] == 'email'
        assert profile['script'] == 'latin'
        assert profile['has_emoji'] == False
    
    def test_consensus_algorithm(self):
        """Test consensus algorithm selection across columns."""
        from fmatch.engine_algorithm_integration import get_consensus_algorithm
        
        # Multiple columns prefer different algorithms
        column_scores = {
            'email': [('Exact', 0.9), ('JaroWinkler', 0.7), ('WRatio', 0.5)],
            'name': [('JaroWinkler', 0.8), ('WRatio', 0.7), ('Exact', 0.3)],
            'company': [('TokenSetRatio', 0.9), ('WRatio', 0.8), ('Exact', 0.2)]
        }
        
        # Equal weights
        consensus = get_consensus_algorithm(column_scores)
        
        # WRatio appears in all three with good scores, might win
        assert consensus in ['WRatio', 'TokenSetRatio', 'JaroWinkler']
        
        # With weights favoring email
        weights = {'email': 2.0, 'name': 1.0, 'company': 0.5}
        consensus = get_consensus_algorithm(column_scores, weights)
        
        # Exact should be stronger now due to email weight
        assert consensus in ['Exact', 'JaroWinkler']


if __name__ == "__main__":
    pytest.main([__file__, '-v'])