"""
Contract validation tests to prevent regressions.
These tests ensure API responses match expected schemas and value ranges.
"""
import pytest
from typing import Dict, Any, List


def validate_score_range(score: float, field_name: str = "score") -> None:
    """Validate score is in 0-1 range."""
    assert score is not None, f"{field_name} must not be None"
    assert 0 <= score <= 1, f"{field_name} must be between 0 and 1, got {score}"


def validate_l2a_suggestion(suggestion: Dict[str, Any]) -> None:
    """Validate L2A suggestion structure."""
    # Required fields
    assert "lead_id" in suggestion
    assert "account_id" in suggestion
    assert "score" in suggestion
    assert "tier" in suggestion
    assert "status" in suggestion
    
    # Score validation
    validate_score_range(suggestion["score"])
    
    # Evidence strength if present
    if "evidence_strength" in suggestion:
        validate_score_range(suggestion["evidence_strength"], "evidence_strength")
    
    # Field score details validation
    if "field_score_details" in suggestion and suggestion["field_score_details"]:
        if isinstance(suggestion["field_score_details"], list):
            for detail in suggestion["field_score_details"]:
                if "score" in detail:
                    # Allow 0-100 or 0-1 in field details
                    score = detail["score"]
                    if score > 1:
                        score = score / 100
                    validate_score_range(score, "field_score_details.score")
    
    # Tier validation
    valid_tiers = ["CERTAIN", "LIKELY", "POSSIBLE", "REVIEW", "UNLIKELY"]
    assert suggestion["tier"] in valid_tiers, f"Invalid tier: {suggestion['tier']}"
    
    # Status validation
    valid_statuses = ["pending", "linked", "rejected", "error"]
    assert suggestion["status"] in valid_statuses, f"Invalid status: {suggestion['status']}"


def validate_dedupe_suggestion(suggestion: Dict[str, Any]) -> None:
    """Validate dedupe suggestion structure."""
    # Required fields
    assert "rec_id_1" in suggestion
    assert "rec_id_2" in suggestion
    assert "score" in suggestion
    assert "status" in suggestion
    
    # Score validation
    validate_score_range(suggestion["score"])
    
    # Ensure no self-matches
    assert suggestion["rec_id_1"] != suggestion["rec_id_2"], "Self-match detected"
    
    # Ensure canonical ordering (rec_id_1 < rec_id_2)
    if isinstance(suggestion["rec_id_1"], str) and isinstance(suggestion["rec_id_2"], str):
        assert suggestion["rec_id_1"] < suggestion["rec_id_2"], \
            f"Non-canonical ordering: {suggestion['rec_id_1']} >= {suggestion['rec_id_2']}"
    
    # Evidence strength if present
    if "evidence_strength" in suggestion:
        validate_score_range(suggestion["evidence_strength"], "evidence_strength")
    
    # Field score details validation
    if "field_score_details" in suggestion and suggestion["field_score_details"]:
        if isinstance(suggestion["field_score_details"], list):
            for detail in suggestion["field_score_details"]:
                if "score" in detail:
                    # Allow 0-100 or 0-1 in field details
                    score = detail["score"]
                    if score > 1:
                        score = score / 100
                    validate_score_range(score, "field_score_details.score")


def validate_merge_plan_response(response: Dict[str, Any]) -> None:
    """Validate merge-plan endpoint response."""
    # Required fields
    assert "cluster_id" in response
    assert "object_type" in response
    assert "primary_id" in response  # Not recommended_primary_id
    assert "fields" in response
    assert "candidates" in response
    
    # Validate fields structure (FieldValue schema)
    for field in response["fields"]:
        assert "field" in field, "Field must have 'field' key (not 'api')"
        assert "values" in field, "Field must have 'values' dict"
        assert "label" in field
        assert "group" in field
        assert "hasConflict" in field
        assert "locked" in field
        
        # Values must be a dict mapping record_id -> value
        assert isinstance(field["values"], dict), "values must be a dict, not a list"
    
    # Validate candidates
    for candidate in response["candidates"]:
        assert "id" in candidate
        assert "score" in candidate
        validate_score_range(candidate["score"])
        
        if "tier" in candidate:
            valid_tiers = ["CERTAIN", "HIGH", "MEDIUM", "POSSIBLE"]
            assert candidate["tier"] in valid_tiers


def validate_domain_anchor_in_mappings(lead: Dict[str, Any], mappings: List[Dict]) -> None:
    """
    Validate that business domain leads have domain anchors in mappings.
    """
    email = lead.get("Email", "").lower()
    if not email:
        return
    
    # Check if business domain
    consumer_domains = ["gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com"]
    is_business = not any(domain in email for domain in consumer_domains)
    
    if is_business:
        # Should have domain/website mapping
        has_domain_anchor = False
        for mapping in mappings:
            src = (mapping.get("source_column") or mapping.get("source") or "").lower()
            ref = (mapping.get("reference_column") or mapping.get("reference") or "").lower()
            if any(field in src for field in ["website", "domain", "email"]) and \
               any(field in ref for field in ["website", "domain"]):
                has_domain_anchor = True
                break
        
        assert has_domain_anchor, \
            f"Business lead with email {email} has no domain anchor in mappings"


class TestL2AContracts:
    """Test L2A API contracts."""
    
    def test_l2a_suggestion_valid(self):
        """Test valid L2A suggestion passes validation."""
        suggestion = {
            "lead_id": "00Q123",
            "account_id": "001456",
            "score": 0.85,
            "tier": "LIKELY",
            "status": "pending",
            "evidence_strength": 0.75,
            "field_score_details": [
                {"source_field": "email", "score": 0.95},
                {"source_field": "company", "score": 75}  # 0-100 scale
            ]
        }
        validate_l2a_suggestion(suggestion)
    
    def test_l2a_suggestion_invalid_score(self):
        """Test L2A suggestion with invalid score fails."""
        suggestion = {
            "lead_id": "00Q123",
            "account_id": "001456",
            "score": 1.5,  # Invalid
            "tier": "LIKELY",
            "status": "pending"
        }
        with pytest.raises(AssertionError, match="score must be between 0 and 1"):
            validate_l2a_suggestion(suggestion)
    
    def test_l2a_suggestion_invalid_tier(self):
        """Test L2A suggestion with invalid tier fails."""
        suggestion = {
            "lead_id": "00Q123",
            "account_id": "001456",
            "score": 0.85,
            "tier": "MAYBE",  # Invalid
            "status": "pending"
        }
        with pytest.raises(AssertionError, match="Invalid tier"):
            validate_l2a_suggestion(suggestion)


class TestDedupeContracts:
    """Test dedupe API contracts."""
    
    def test_dedupe_suggestion_valid(self):
        """Test valid dedupe suggestion passes validation."""
        suggestion = {
            "rec_id_1": "001123",
            "rec_id_2": "001456",
            "score": 0.75,
            "status": "pending",
            "evidence_strength": 0.65,
            "field_score_details": [
                {"source_field": "name", "score": 0.85},
                {"source_field": "phone", "score": 90}  # 0-100 scale
            ]
        }
        validate_dedupe_suggestion(suggestion)
    
    def test_dedupe_self_match_fails(self):
        """Test dedupe with self-match fails."""
        suggestion = {
            "rec_id_1": "001123",
            "rec_id_2": "001123",  # Self-match
            "score": 1.0,
            "status": "pending"
        }
        with pytest.raises(AssertionError, match="Self-match detected"):
            validate_dedupe_suggestion(suggestion)
    
    def test_dedupe_non_canonical_fails(self):
        """Test dedupe with non-canonical ordering fails."""
        suggestion = {
            "rec_id_1": "001456",  # Should be second
            "rec_id_2": "001123",  # Should be first
            "score": 0.75,
            "status": "pending"
        }
        with pytest.raises(AssertionError, match="Non-canonical ordering"):
            validate_dedupe_suggestion(suggestion)
    
    def test_dedupe_invalid_score_range(self):
        """Test dedupe with out-of-range score fails."""
        suggestion = {
            "rec_id_1": "001123",
            "rec_id_2": "001456",
            "score": -0.1,  # Invalid
            "status": "pending"
        }
        with pytest.raises(AssertionError, match="score must be between 0 and 1"):
            validate_dedupe_suggestion(suggestion)


class TestMergePlanContracts:
    """Test merge-plan API contracts."""
    
    def test_merge_plan_valid(self):
        """Test valid merge plan passes validation."""
        response = {
            "cluster_id": "cluster_123",
            "object_type": "Lead",
            "primary_id": "001123",  # Correct key name
            "fields": [
                {
                    "field": "Name",  # Correct key (not 'api')
                    "label": "Full Name",
                    "group": "Identity",
                    "hasConflict": False,
                    "locked": False,
                    "values": {  # Dict, not list
                        "001123": "John Doe",
                        "001456": "John Doe"
                    }
                },
                {
                    "field": "Email",
                    "label": "Email Address",
                    "group": "Contact",
                    "hasConflict": True,
                    "locked": False,
                    "values": {
                        "001123": "john@acme.com",
                        "001456": "jdoe@acme.com"
                    }
                }
            ],
            "candidates": [
                {"id": "001123", "score": 0.95, "tier": "CERTAIN"},
                {"id": "001456", "score": 0.85, "tier": "HIGH"}
            ]
        }
        validate_merge_plan_response(response)
    
    def test_merge_plan_wrong_field_key(self):
        """Test merge plan with wrong field key fails."""
        response = {
            "cluster_id": "cluster_123",
            "object_type": "Lead",
            "primary_id": "001123",
            "fields": [
                {
                    "api": "Name",  # Wrong key (should be 'field')
                    "label": "Full Name",
                    "group": "Identity",
                    "hasConflict": False,
                    "locked": False,
                    "values": {"001123": "John Doe"}
                }
            ],
            "candidates": []
        }
        with pytest.raises(AssertionError, match="Field must have 'field' key"):
            validate_merge_plan_response(response)
    
    def test_merge_plan_wrong_values_type(self):
        """Test merge plan with wrong values type fails."""
        response = {
            "cluster_id": "cluster_123",
            "object_type": "Lead",
            "primary_id": "001123",
            "fields": [
                {
                    "field": "Name",
                    "label": "Full Name",
                    "group": "Identity",
                    "hasConflict": False,
                    "locked": False,
                    "values": [  # Wrong type (should be dict)
                        {"record_id": "001123", "value": "John Doe"}
                    ]
                }
            ],
            "candidates": []
        }
        with pytest.raises(AssertionError, match="values must be a dict"):
            validate_merge_plan_response(response)
    
    def test_merge_plan_wrong_primary_key(self):
        """Test merge plan with wrong primary key name fails."""
        response = {
            "cluster_id": "cluster_123",
            "object_type": "Lead",
            "recommended_primary_id": "001123",  # Wrong key name
            # Missing "primary_id"
            "fields": [],
            "candidates": []
        }
        with pytest.raises(AssertionError, match="primary_id"):
            validate_merge_plan_response(response)


class TestDomainAnchorContracts:
    """Test domain anchor validation."""
    
    def test_business_lead_has_domain_anchor(self):
        """Test business email leads have domain anchors."""
        lead = {"Email": "john@acme.com", "Company": "Acme Corp"}
        mappings = [
            {"source_column": "Email", "reference_column": "Website"},
            {"source_column": "Company", "reference_column": "Name"}
        ]
        validate_domain_anchor_in_mappings(lead, mappings)
    
    def test_business_lead_missing_domain_anchor_fails(self):
        """Test business email without domain anchor fails."""
        lead = {"Email": "john@acme.com", "Company": "Acme Corp"}
        mappings = [
            {"source_column": "Company", "reference_column": "Name"},
            {"source_column": "Phone", "reference_column": "Phone"}
        ]
        with pytest.raises(AssertionError, match="no domain anchor"):
            validate_domain_anchor_in_mappings(lead, mappings)
    
    def test_consumer_email_no_domain_anchor_ok(self):
        """Test consumer email without domain anchor is OK."""
        lead = {"Email": "john@gmail.com", "Company": "Acme Corp"}
        mappings = [
            {"source_column": "Company", "reference_column": "Name"},
            {"source_column": "Phone", "reference_column": "Phone"}
        ]
        # Should not raise
        validate_domain_anchor_in_mappings(lead, mappings)


if __name__ == "__main__":
    # Run basic smoke tests
    print("Testing L2A contracts...")
    test_l2a = TestL2AContracts()
    test_l2a.test_l2a_suggestion_valid()
    print("[OK] L2A suggestion validation passed")
    
    print("\nTesting dedupe contracts...")
    test_dedupe = TestDedupeContracts()
    test_dedupe.test_dedupe_suggestion_valid()
    print("[OK] Dedupe suggestion validation passed")
    
    print("\nTesting merge-plan contracts...")
    test_merge = TestMergePlanContracts()
    test_merge.test_merge_plan_valid()
    print("[OK] Merge plan validation passed")
    
    print("\nAll contract validation tests passed!")