"""Tests for CodeValidationAI clinical plausibility validator (chain-based)."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from claim_validator.constants import Severity
from claim_validator.models.deidentified import (
    DeidentifiedClaim,
    DeidentifiedLineData,
)
from claim_validator.models.results import ValidatorOutput
from claim_validator.validators.ai.code_validation import (
    CodeValidationAI,
)

# --- Helpers ---


def _make_claim(
    **overrides: object,
) -> DeidentifiedClaim:
    """Create a de-identified claim for testing."""
    defaults: dict = {
        "billing_provider_npi": "1234567893",
        "patient_age": 45,
        "patient_gender": "M",
        "payer_id": "BCBS01",
        "diagnosis_codes": [
            {"code": "J06.9", "pointer": 1},
        ],
        "lines": [
            DeidentifiedLineData(
                procedure_code="99213",
                diagnosis_pointers=[1],
                charge_amount=150.0,
            ),
        ],
    }
    defaults.update(overrides)
    return DeidentifiedClaim(**defaults)


def _make_mock_chain_result(findings=None, confidence=1.0, reasoning="OK"):
    """Create a mock chain result."""
    result = MagicMock()
    if findings is None:
        findings = []
    result.to_validator_output.return_value = ValidatorOutput(
        validator_name="CodeValidationAI",
        findings=findings,
    )
    return result


# --- Tests ---


class TestCodeValidationAICleanClaim:
    """Test zero findings for valid clinical coding."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_valid_coding_returns_empty(self, mock_invoke) -> None:
        mock_invoke.return_value = _make_mock_chain_result()
        mock_chain = MagicMock()
        validator = CodeValidationAI(chain=mock_chain)
        result = validator.validate_deidentified(_make_claim())
        assert isinstance(result, ValidatorOutput)
        assert len(result.findings) == 0

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_validator_name(self, mock_invoke) -> None:
        mock_invoke.return_value = _make_mock_chain_result()
        mock_chain = MagicMock()
        validator = CodeValidationAI(chain=mock_chain)
        result = validator.validate_deidentified(_make_claim())
        assert result.validator_name == "CodeValidationAI"


class TestCodeValidationAIWithFindings:
    """Test findings from chain produce correct output."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_findings_returned(self, mock_invoke) -> None:
        from claim_validator.models.results import Finding
        finding = Finding(
            code="AI_CLINICAL_IMPLAUSIBILITY",
            message="Obstetric procedure for male patient",
            severity=Severity.WARNING,
            field_name="procedure_code",
            line_number=1,
        )
        mock_invoke.return_value = _make_mock_chain_result(findings=[finding])
        mock_chain = MagicMock()
        validator = CodeValidationAI(chain=mock_chain)
        result = validator.validate_deidentified(_make_claim(patient_gender="M"))
        assert len(result.findings) == 1
        f = result.findings[0]
        assert f.code == "AI_CLINICAL_IMPLAUSIBILITY"
        assert f.severity == Severity.WARNING


class TestCodeValidationAIChainFallback:
    """Test chain failure produces empty results via fallback."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_chain_returns_none_fallback(self, mock_invoke) -> None:
        mock_invoke.return_value = None
        mock_chain = MagicMock()
        validator = CodeValidationAI(chain=mock_chain)
        result = validator.validate_deidentified(_make_claim())
        assert len(result.findings) == 0


class TestCodeValidationAINoPHI:
    """Test no PHI in any findings."""

    def test_no_phi_in_system_prompt(self) -> None:
        assert hasattr(CodeValidationAI, "SYSTEM_PROMPT")
        prompt = CodeValidationAI.SYSTEM_PROMPT
        phi_terms = [
            "patient name",
            "social security",
            "date of birth",
            "address",
            "phone",
            "email",
        ]
        lower = prompt.lower()
        for term in phi_terms:
            assert term not in lower or "strip" in lower
