"""
Real API contract tests to validate endpoints return properly normalized data.
"""
import pytest
from fastapi.testclient import TestClient
import sys
import os

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'src'))


@pytest.fixture
def client():
    """Create test client."""
    from fmatch.saas.main import app
    return TestClient(app)


class TestL2AScoresInRange:
    """Test L2A scores are properly normalized."""
    
    @pytest.mark.skip(reason="Requires running server and data")
    def test_l2a_scores_in_range(self, client):
        """Test all L2A scores are in 0-1 range."""
        r = client.get("/api/v2/l2a/suggestions?include=explanations")
        assert r.status_code == 200
        
        data = r.json()
        for item in data.get("items", []):
            # Main score
            assert 0 <= item["score"] <= 1, f"Score out of range: {item['score']}"
            
            # Evidence strength if present
            if "evidence_strength" in item:
                assert 0 <= item["evidence_strength"] <= 1, \
                    f"Evidence strength out of range: {item['evidence_strength']}"
            
            # Field score details
            for detail in item.get("field_score_details", []):
                if "score" in detail:
                    assert 0 <= detail["score"] <= 1, \
                        f"Field score out of range: {detail['score']}"


class TestDedupeScoresInRange:
    """Test dedupe scores are properly normalized."""
    
    @pytest.mark.skip(reason="Requires running server and data")
    def test_dedupe_scores_in_range(self, client):
        """Test all dedupe scores are in 0-1 range."""
        r = client.get("/api/v2/dedupe/suggestions?object_type=Lead")
        assert r.status_code == 200
        
        data = r.json()
        for item in data.get("items", []):
            # Main score
            assert 0 <= item["score"] <= 1, f"Score out of range: {item['score']}"
            
            # Evidence strength if present  
            if "evidence_strength" in item:
                assert 0 <= item["evidence_strength"] <= 1, \
                    f"Evidence strength out of range: {item['evidence_strength']}"
            
            # Field score details
            for detail in item.get("field_score_details", []):
                if "score" in detail:
                    assert 0 <= detail["score"] <= 1, \
                        f"Field score out of range: {detail['score']}"
            
            # Verify canonical ordering
            assert item["rec_id_1"] < item["rec_id_2"], \
                f"Non-canonical ordering: {item['rec_id_1']} >= {item['rec_id_2']}"
            
            # Verify no self-matches
            assert item["rec_id_1"] != item["rec_id_2"], \
                f"Self-match found: {item['rec_id_1']}"


class TestMergePlanContract:
    """Test merge-plan endpoint contract."""
    
    @pytest.mark.skip(reason="Requires running server and cluster data")  
    def test_merge_plan_schema(self, client):
        """Test merge-plan returns correct schema."""
        # Would need a valid cluster_id
        cluster_id = "test_cluster"
        r = client.get(f"/api/v2/dedupe/merge-plan/{cluster_id}?object_type=Lead")
        
        if r.status_code == 200:
            data = r.json()
            
            # Required top-level fields
            assert "cluster_id" in data
            assert "object_type" in data
            assert "primary_id" in data  # Not recommended_primary_id
            assert "fields" in data
            assert "candidates" in data
            
            # Fields structure
            for field in data["fields"]:
                assert "field" in field, "Missing 'field' key"
                assert "values" in field, "Missing 'values' key"
                assert isinstance(field["values"], dict), "values must be dict"
                assert "label" in field
                assert "group" in field
                assert "hasConflict" in field
                assert "locked" in field
            
            # Conflict count validation
            if "conflict_count" in data:
                actual = sum(1 for f in data["fields"] if f["hasConflict"])
                assert data["conflict_count"] == actual, \
                    f"Conflict count mismatch: {data['conflict_count']} != {actual}"
            
            # Candidate scores
            for candidate in data["candidates"]:
                assert "id" in candidate
                assert "score" in candidate
                assert 0 <= candidate["score"] <= 1


class TestExplainNormalization:
    """Test match_explain endpoint normalization."""
    
    @pytest.mark.skip(reason="Requires running server and data")
    def test_explain_scores_normalized(self, client):
        """Test explain endpoint returns normalized scores."""
        # Would need valid IDs
        lead_id = "00Q123"
        account_id = "001456"
        
        r = client.get(f"/api/v2/match/explain?lead_id={lead_id}&account_id={account_id}")
        
        if r.status_code == 200:
            data = r.json()
            
            # Overall score
            if "score" in data:
                assert 0 <= data["score"] <= 1
            
            # Evidence strength
            if "evidence_strength" in data:
                assert 0 <= data["evidence_strength"] <= 1
            
            # Field contributions
            for contrib in data.get("field_contributions", []):
                if "score" in contrib:
                    assert 0 <= contrib["score"] <= 1
                if "confidence" in contrib:
                    assert 0 <= contrib["confidence"] <= 1


# Unit tests that don't require server
class TestScoreNormalizationUnit:
    """Unit tests for score normalization."""
    
    def test_norm01_handles_all_cases(self):
        """Test norm01 function with various inputs."""
        from fmatch.saas.utils.score_utils import norm01
        import math
        
        # Normal 0-1 range
        assert norm01(0.5) == 0.5
        assert norm01(0.0) == 0.0
        assert norm01(1.0) == 1.0
        
        # 0-100 range
        assert norm01(50) == 0.5
        assert norm01(95) == 0.95
        assert norm01(100) == 1.0
        
        # Out of range
        assert norm01(150) == 1.0
        assert norm01(-10) == 0.0
        assert norm01(-0.5) == 0.0
        assert norm01(1.5) == 1.0
        
        # Edge cases
        assert norm01(None) == 0.0
        assert norm01("0.8") == 0.8
        assert norm01("invalid") == 0.0
        
        # NEW: Percent strings
        assert norm01("85%") == 0.85
        assert norm01("100%") == 1.0
        assert norm01("150%") == 1.0  # Clamped
        assert norm01("-5%") == 0.0   # Clamped
        
        # NEW: NaN values
        assert norm01(float('nan')) == 0.0
        assert norm01(math.nan) == 0.0
    
    def test_normalize_field_scores(self):
        """Test field score normalization."""
        from fmatch.saas.utils.score_utils import normalize_field_scores
        
        # Mixed scales
        input_scores = [
            {"field": "email", "score": 95},  # 0-100
            {"field": "name", "score": 0.8},   # 0-1
            {"field": "phone", "score": 150}   # Out of range
        ]
        
        result = normalize_field_scores(input_scores)
        
        assert result[0]["score"] == 0.95
        assert result[1]["score"] == 0.8
        assert result[2]["score"] == 1.0
        
        # JSON string input
        import json
        json_input = json.dumps(input_scores)
        result2 = normalize_field_scores(json_input)
        assert result2[0]["score"] == 0.95
    
    def test_get_confidence_tier(self):
        """Test confidence tier calculation."""
        from fmatch.saas.utils.score_utils import get_confidence_tier
        
        assert get_confidence_tier(0.95) == "CERTAIN"
        assert get_confidence_tier(0.85) == "LIKELY"
        assert get_confidence_tier(0.65) == "POSSIBLE"
        assert get_confidence_tier(0.35) == "REVIEW"
        assert get_confidence_tier(0.15) == "UNLIKELY"
        
        # With custom thresholds
        custom = {"certain": 0.80, "likely": 0.60}
        assert get_confidence_tier(0.85, custom) == "CERTAIN"
        assert get_confidence_tier(0.65, custom) == "LIKELY"


class TestDedupeCorrectnessUnit:
    """Unit tests for dedupe correctness improvements."""
    
    def test_dedupe_pairs_unique_and_best_score(self):
        """Test that dedupe keeps unique pairs with best scores."""
        import pandas as pd
        
        # Simulate duplicate pairs with different scores
        matches = pd.DataFrame({
            "rec_id_1": ["A", "A", "B", "B"],
            "rec_id_2": ["B", "B", "C", "C"],
            "score": [0.8, 0.9, 0.7, 0.85],
            "detail": ["low", "high", "first", "second"]
        })
        
        # Expected behavior: keep highest score per pair
        result = (matches
            .sort_values(["rec_id_1", "rec_id_2", "score"], ascending=[True, True, False])
            .groupby(["rec_id_1", "rec_id_2"], as_index=False)
            .first()
        )
        
        assert len(result) == 2
        assert result.iloc[0]["score"] == 0.9  # Best score for A-B
        assert result.iloc[0]["detail"] == "high"  # Detail from best match
        assert result.iloc[1]["score"] == 0.85  # Best score for B-C
        assert result.iloc[1]["detail"] == "second"
    
    def test_domain_extraction(self):
        """Test registrable domain extraction."""
        # Would need to import the actual function
        # from fmatch.saas.services.engine_client import EngineClient
        # client = EngineClient()
        
        # Test cases for domain extraction
        test_cases = [
            ("user@example.com", "example.com"),
            ("admin@subdomain.example.co.uk", "subdomain.example.co.uk"),
            ("https://www.example.com/path", "example.com"),
            ("http://example.org?query=1", "example.org"),
            ("invalid-no-dot", None),
            ("", None),
            (None, None),
        ]
        
        # Would test with actual function
        # for input_val, expected in test_cases:
        #     assert client._extract_registrable_domain(input_val) == expected
    
    def test_safe_score_column_default(self):
        """Test that missing score column gets safe default."""
        import pandas as pd
        
        # DataFrame without score column
        matches = pd.DataFrame({
            "source_id": ["A", "B"],
            "target_id": ["C", "D"],
        })
        
        # Add default score if missing
        if "score" not in matches.columns:
            matches["score"] = 0.0
        
        assert "score" in matches.columns
        assert (matches["score"] == 0.0).all()


class TestExplainNormalizationUnit:
    """Unit tests for explain route normalization."""
    
    def test_explain_scores_normalized(self):
        """Test explain normalizes all scores."""
        from fmatch.saas.utils.score_utils import norm01
        
        # Simulate explain data
        field_details = [
            {"field": "email", "score": 95, "weight": 0.3},  # 0-100
            {"field": "name", "score": "85%", "weight": 0.4},  # Percent
            {"field": "phone", "score": 0.7, "weight": 0.3},  # 0-1
        ]
        
        # Normalize all scores
        for d in field_details:
            d["score"] = norm01(d.get("score"))
        
        assert field_details[0]["score"] == 0.95
        assert field_details[1]["score"] == 0.85
        assert field_details[2]["score"] == 0.7
    
    def test_evidence_strength_calculation(self):
        """Test evidence strength is properly weighted average."""
        from fmatch.saas.utils.score_utils import norm01
        
        details = [
            {"score": 90, "weight": 0.5},
            {"score": 80, "weight": 0.3},
            {"score": 70, "weight": 0.2},
        ]
        
        # Calculate weighted average
        total_w = total_ws = 0.0
        for d in details:
            w = float(d.get("weight", 0))
            s = norm01(d.get("score"))
            total_w += w
            total_ws += w * s
        
        evidence_strength = total_ws / total_w if total_w > 0 else 0.0
        
        # (0.9*0.5 + 0.8*0.3 + 0.7*0.2) / 1.0 = 0.83
        assert abs(evidence_strength - 0.83) < 0.001