"""Tests for ensemble explanations module"""
from src.fmatch.saas.services.ensemble_explanations import (
    summarize_evidence,
    compact_explanations,
    build_ensemble_summary,
    _ensure_contributions
)


def _mk_field_score(overrides=None):
    """Helper to create a field score detail"""
    base = {
        "source_column": "Company",
        "reference_column": "Name",
        "algorithm": "WRatio",
        "score": 0.92,
        "weight": 0.65
    }
    if overrides:
        base.update(overrides)
    return base


class TestEnsembleExplanations:
    """Test suite for ensemble explanation functions"""

    def test_ensure_contributions_synthesizes_when_missing(self):
        """Test that contributions are synthesized from weight*score when not present"""
        fsd = [
            {"source_column": "Website", "reference_column": "Website", "algorithm": "Domain", "score": 1.0, "weight": 0.55},
            {"source_column": "Company", "reference_column": "Name", "algorithm": "WRatio", "score": 0.92, "weight": 0.65},
            {"source_column": "Phone", "reference_column": "Phone", "algorithm": "Exact", "score": 1.0, "weight": 0.25},
        ]

        result = _ensure_contributions(fsd)

        # Check all have contributions
        assert all("contribution" in r for r in result)

        # Check contributions sum to approximately 1.0
        total_contrib = sum(r["contribution"] for r in result)
        assert 0.99 <= total_contrib <= 1.01

        # Check relative contributions match weight*score ratios
        # Company should have highest contribution (0.65 * 0.92 = 0.598)
        company_contrib = next(r["contribution"] for r in result if r["source_column"] == "Company")
        assert company_contrib > 0.35  # Should be significant

    def test_ensure_contributions_preserves_existing(self):
        """Test that existing contributions are preserved"""
        fsd = [
            {"source_column": "Website", "contribution": 0.4},
            {"source_column": "Company", "contribution": 0.6},
        ]

        result = _ensure_contributions(fsd)

        assert result[0]["contribution"] == 0.4
        assert result[1]["contribution"] == 0.6

    def test_summarize_evidence_with_multi_algo(self):
        """Test evidence summary generation with multi-algorithm support"""
        fsd = [
            {"source_column": "Website", "reference_column": "Website", "algorithm": "Domain", "score": 1.0, "weight": 0.55},
            {"source_column": "Company", "reference_column": "Name", "algorithm": "WRatio", "score": 0.92, "weight": 0.65},
            {"source_column": "Phone", "reference_column": "Phone", "algorithm": "Exact", "score": 1.0, "weight": 0.25},
        ]

        summary = summarize_evidence(fsd, use_multi_algo=True)

        assert summary["ensemble_used"] is True
        assert summary["evidence_count"] >= 2
        assert 0.0 <= summary["evidence_strength"] <= 1.0
        assert "domain_verified" in summary["evidence_flags"]
        assert "name_strong" in summary["evidence_flags"]
        assert "phone_present" in summary["evidence_flags"]

    def test_summarize_evidence_flags_generation(self):
        """Test that appropriate flags are generated based on field scores"""
        # Test domain verification
        fsd = [{"source_column": "Website", "reference_column": "Website", "score": 0.99, "weight": 1.0}]
        summary = summarize_evidence(fsd)
        assert "domain_verified" in summary["evidence_flags"]

        # Test name matching
        fsd = [{"source_column": "Company", "reference_column": "Name", "score": 0.88, "weight": 1.0}]
        summary = summarize_evidence(fsd)
        assert "name_strong" in summary["evidence_flags"]

        # Test phone exact match
        fsd = [{"source_column": "Phone", "reference_column": "Phone", "score": 0.99, "weight": 1.0}]
        summary = summarize_evidence(fsd)
        assert "phone_exact" in summary["evidence_flags"]

        # Test address signal
        fsd = [{"source_column": "City", "reference_column": "BillingCity", "score": 0.75, "weight": 1.0}]
        summary = summarize_evidence(fsd)
        assert "address_signal" in summary["evidence_flags"]

    def test_compact_explanations_top_n(self):
        """Test that compact explanations returns top N contributors"""
        fsd = [
            {"source_column": "Website", "reference_column": "Website", "algorithm": "Domain", "score": 1.0, "weight": 0.55},
            {"source_column": "Company", "reference_column": "Name", "algorithm": "WRatio", "score": 0.92, "weight": 0.65},
            {"source_column": "Phone", "reference_column": "Phone", "algorithm": "Exact", "score": 1.0, "weight": 0.25},
            {"source_column": "City", "reference_column": "BillingCity", "algorithm": "Fuzzy", "score": 0.3, "weight": 0.1},
            {"source_column": "State", "reference_column": "BillingState", "algorithm": "Exact", "score": 0.2, "weight": 0.1},
            {"source_column": "Industry", "reference_column": "Industry", "algorithm": "Exact", "score": 0.1, "weight": 0.05},
        ]

        explanations = compact_explanations(fsd)

        # Should return at most MAX_EXPL (5) explanations
        assert 1 <= len(explanations) <= 5

        # Should include high-scoring fields
        field_pairs = [e["field"] for e in explanations]
        assert "Website->Website" in field_pairs
        assert "Company->Name" in field_pairs

        # Check structure
        for exp in explanations:
            assert "field" in exp
            assert "type" in exp
            assert "score" in exp
            assert "algo" in exp
            assert "note" in exp

    def test_compact_explanations_type_detection(self):
        """Test that field types are correctly detected"""
        fsd = [
            {"source_column": "Website", "reference_column": "Website", "algorithm": "Domain", "score": 1.0, "weight": 1.0},
            {"source_column": "Company", "reference_column": "Name", "algorithm": "WRatio", "score": 0.92, "weight": 1.0},
            {"source_column": "Phone", "reference_column": "Phone", "algorithm": "Exact", "score": 0.99, "weight": 1.0},
            {"source_column": "Email", "reference_column": "Email", "algorithm": "Exact", "score": 0.95, "weight": 1.0},
        ]

        explanations = compact_explanations(fsd)

        # Check type detection
        type_map = {e["field"]: e["type"] for e in explanations}
        assert type_map.get("Website->Website") == "domain_verified"
        assert type_map.get("Company->Name") == "name_similarity"
        assert type_map.get("Phone->Phone") == "phone_exact"
        assert type_map.get("Email->Email") == "email_match"

    def test_build_ensemble_summary(self):
        """Test run-level ensemble summary generation"""
        items = [
            {
                "ensemble_used": True,
                "evidence_flags": ["domain_verified", "name_strong"],
                "reasons": {"Website->Website": 1.0, "Company->Name": 0.92}
            },
            {
                "ensemble_used": True,
                "evidence_flags": ["domain_verified", "phone_present"],
                "reasons": {"Website->Website": 0.99, "Phone->Phone": 0.8}
            },
            {
                "ensemble_used": False,
                "evidence_flags": ["name_strong"],
                "reasons": {"Company->Name": 0.88}
            },
        ]

        summary = build_ensemble_summary(items, present_only_scoring=True, multi_algo_enabled=True)

        assert summary["used"] is True
        assert summary["multi_algo_fields"] == 2  # Based on reasons in first item
        assert summary["domain_verified_pct"] == 67  # 2 out of 3 items
        assert summary["present_only_scoring"] is True

    def test_build_ensemble_summary_empty(self):
        """Test ensemble summary with no items"""
        summary = build_ensemble_summary([], present_only_scoring=False)

        assert summary["used"] is False
        assert summary["multi_algo_fields"] == 0
        assert summary["domain_verified_pct"] == 0
        assert summary["present_only_scoring"] is False

    def test_ascii_arrow_compatibility(self):
        """Test that field pairs use ASCII arrow for Windows compatibility"""
        fsd = [{"source_column": "Company", "reference_column": "Name", "score": 0.9, "weight": 1.0}]

        explanations = compact_explanations(fsd)

        # Check that we use ASCII arrow -> not Unicode →
        assert "->" in explanations[0]["field"]
        assert "→" not in explanations[0]["field"]
        assert explanations[0]["field"] == "Company->Name"

    def test_contribution_threshold_filtering(self):
        """Test that only significant contributors are included in evidence"""
        fsd = [
            {"source_column": "Website", "reference_column": "Website", "score": 0.95, "weight": 0.5},
            {"source_column": "Company", "reference_column": "Name", "score": 0.90, "weight": 0.4},
            {"source_column": "Industry", "reference_column": "Industry", "score": 0.1, "weight": 0.05},  # Low contribution
            {"source_column": "Rating", "reference_column": "Rating", "score": 0.05, "weight": 0.02},  # Very low
        ]

        summary = summarize_evidence(fsd)

        # Should only count significant contributors
        assert summary["evidence_count"] <= 3  # Low contributors filtered out
        assert summary["evidence_strength"] > 0.5  # Dominated by high scorers
