"""Tests for PriorAuthInterpreterAI and pipeline AI phase integration."""

from __future__ import annotations

import json
from datetime import date
from unittest.mock import MagicMock, Mock, patch

import pytest

from claim_validator.conf import ClaimValidatorSettings
from claim_validator.constants import Severity
from claim_validator.exceptions import LLMError
from claim_validator.models.results import Finding
from claim_validator.prior_auth.constants import CertificationActionCode
from claim_validator.prior_auth.deidentifier import PriorAuthDeidentifier
from claim_validator.prior_auth.models.deidentified import (
    DeidentifiedPriorAuthError,
    DeidentifiedPriorAuthResponse,
    DeidentifiedServiceLineDecision,
)
from claim_validator.prior_auth.models.request import (
    PriorAuthRequest,
    ServiceLine,
    SubscriberInfo,
)
from claim_validator.prior_auth.models.response import (
    PriorAuthError,
    PriorAuthResponse,
    ServiceLineDecision,
)
from claim_validator.prior_auth.pipeline import PriorAuthPipeline
from claim_validator.prior_auth.validators.ai.interpreter import (
    PriorAuthInterpreterAI,
)

# ---------------------------------------------------------------------------
# Helpers — mock chain results
# ---------------------------------------------------------------------------


def _make_chain_result(summary: str, findings: list[Finding] | None = None):
    """Build a mock chain result mimicking PAInterpreterOutput."""
    result = MagicMock()
    result.reasoning = summary
    from claim_validator.models.results import ValidatorOutput
    result.to_validator_output.return_value = ValidatorOutput(
        validator_name="PriorAuthInterpreterAI",
        findings=findings or [],
    )
    return result


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def chain_result_approved():
    """Chain result for PA approval."""
    return _make_chain_result(
        summary="Prior authorization approved for knee replacement (CPT 27447).",
        findings=[
            Finding(
                code="AI_PA_APPROVAL_SUMMARY",
                message="Authorization approved for 3 units",
                severity=Severity.WARNING,
                field_name="",
                suggestion="Schedule procedure within authorization window",
            ),
        ],
    )


@pytest.fixture()
def chain_result_denial():
    """Chain result for PA denial."""
    return _make_chain_result(
        summary="Prior authorization denied. Medical necessity not established.",
        findings=[
            Finding(
                code="AI_PA_DENIAL_EXPLAINED",
                message="Denial due to insufficient clinical documentation",
                severity=Severity.WARNING,
                field_name="",
                suggestion="File appeal with additional clinical notes and peer-to-peer review",
            ),
        ],
    )


@pytest.fixture()
def chain_result_pended():
    """Chain result for pended case."""
    return _make_chain_result(
        summary="Prior authorization pended pending additional clinical information.",
        findings=[
            Finding(
                code="AI_PA_PENDED_NEXT_STEPS",
                message="Payer requires additional clinical documentation",
                severity=Severity.WARNING,
                field_name="",
                suggestion="Submit operative notes and imaging results within 10 business days",
            ),
        ],
    )


@pytest.fixture()
def chain_result_empty():
    """Chain result with no findings."""
    return _make_chain_result(
        summary="Standard authorization approved.",
        findings=[],
    )


@pytest.fixture()
def deidentified_approved_response():
    """De-identified response: A1 action code."""
    return DeidentifiedPriorAuthResponse(
        action_code=CertificationActionCode.CERTIFIED_IN_TOTAL,
        decision_reason_code="01",
        effective_year=2024,
        expiration_year=2025,
        service_line_decisions=[
            DeidentifiedServiceLineDecision(
                cpt_code="27447",
                action_code=CertificationActionCode.CERTIFIED_IN_TOTAL,
                approved_quantity=3,
            ),
        ],
        errors=[],
    )


@pytest.fixture()
def deidentified_denied_response():
    """De-identified response: A3 action code."""
    return DeidentifiedPriorAuthResponse(
        action_code=CertificationActionCode.NOT_CERTIFIED,
        decision_reason_code="05",
        service_line_decisions=[],
        errors=[
            DeidentifiedPriorAuthError(
                rejection_code="72",
                follow_up_code="C",
            ),
        ],
    )


@pytest.fixture()
def deidentified_pended_response():
    """De-identified response: A4 action code."""
    return DeidentifiedPriorAuthResponse(
        action_code=CertificationActionCode.PENDED,
        decision_reason_code="15",
        service_line_decisions=[],
        errors=[],
    )


@pytest.fixture()
def full_phi_pa_response():
    """Full PHI PA response."""
    return PriorAuthResponse(
        action_code=CertificationActionCode.CERTIFIED_IN_TOTAL,
        authorization_number="AUTH-2024-JD-98765",
        effective_date=date(2024, 3, 15),
        expiration_date=date(2025, 3, 15),
        decision_reason_code="01",
        decision_reason_description="Approved for patient John Smith MEM001",
        service_line_decisions=[
            ServiceLineDecision(
                cpt_code="27447",
                action_code=CertificationActionCode.CERTIFIED_IN_TOTAL,
                authorization_number="AUTH-SL-001",
                approved_quantity=3,
                denied_reason=None,
            ),
        ],
        errors=[
            PriorAuthError(
                rejection_code="72",
                follow_up_code="C",
                message="Subscriber John Smith (ID: MEM001) requires review",
                suggested_fix="Contact plan for member MEM001",
            ),
        ],
        raw_response={"subscriber": {"firstName": "John", "memberId": "MEM001"}},
    )


@pytest.fixture()
def valid_pa_request():
    """Valid PriorAuthRequest."""
    return PriorAuthRequest(
        requester_npi="1234567893",
        subscriber=SubscriberInfo(
            member_id="MEM001",
            first_name="Jane",
            last_name="Doe",
            dob=date(1985, 3, 15),
        ),
        diagnosis_codes=["M17.11"],
        service_lines=[
            ServiceLine(
                cpt_code="27447",
                quantity=1,
                from_date=date(2026, 4, 1),
            ),
        ],
    )


# ---------------------------------------------------------------------------
# TestInterpreterDirect
# ---------------------------------------------------------------------------


class TestInterpreterDirect:
    """Test PriorAuthInterpreterAI.interpret() directly via chain."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_approved_case_summary(
        self, mock_invoke, chain_result_approved, deidentified_approved_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        summary, findings = interpreter.interpret(deidentified_approved_response)

        assert summary
        assert isinstance(summary, str)
        assert len(summary) > 10

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_findings_have_ai_pa_prefix(
        self, mock_invoke, chain_result_approved, deidentified_approved_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        _, findings = interpreter.interpret(deidentified_approved_response)

        assert len(findings) >= 1
        for f in findings:
            assert f.code.startswith("AI_PA_")

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_empty_response_interpretation(self, mock_invoke, chain_result_empty):
        mock_invoke.return_value = chain_result_empty
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        empty_response = DeidentifiedPriorAuthResponse()
        summary, findings = interpreter.interpret(empty_response)

        assert summary
        assert findings == []


# ---------------------------------------------------------------------------
# TestInterpreterDenial
# ---------------------------------------------------------------------------


class TestInterpreterDenial:
    """Test interpretation of denial responses."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_denial_summary(
        self, mock_invoke, chain_result_denial, deidentified_denied_response,
    ):
        mock_invoke.return_value = chain_result_denial
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        summary, findings = interpreter.interpret(deidentified_denied_response)

        assert "denied" in summary.lower()
        assert any(f.code == "AI_PA_DENIAL_EXPLAINED" for f in findings)

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_denial_has_appeal_suggestion(
        self, mock_invoke, chain_result_denial, deidentified_denied_response,
    ):
        mock_invoke.return_value = chain_result_denial
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        _, findings = interpreter.interpret(deidentified_denied_response)

        denial_findings = [f for f in findings if f.code == "AI_PA_DENIAL_EXPLAINED"]
        assert len(denial_findings) == 1
        assert "appeal" in denial_findings[0].suggestion.lower()


# ---------------------------------------------------------------------------
# TestInterpreterPended
# ---------------------------------------------------------------------------


class TestInterpreterPended:
    """Test interpretation of pended responses."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_pended_summary(
        self, mock_invoke, chain_result_pended, deidentified_pended_response,
    ):
        mock_invoke.return_value = chain_result_pended
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        summary, findings = interpreter.interpret(deidentified_pended_response)

        assert "pended" in summary.lower()
        assert any(f.code == "AI_PA_PENDED_NEXT_STEPS" for f in findings)


# ---------------------------------------------------------------------------
# TestPipelineAIPhase
# ---------------------------------------------------------------------------


class TestPipelineAIPhase:
    """Test PriorAuthPipeline with AI phase enabled."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_full_pipeline_with_ai(
        self, mock_invoke, chain_result_approved, valid_pa_request, full_phi_pa_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        pipeline = PriorAuthPipeline(
            rule_validators=[],
            ai_interpreter=interpreter,
        )
        result = pipeline.run(valid_pa_request, response=full_phi_pa_response)

        assert result.ai_summary is not None
        assert len(result.ai_summary) > 0

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_full_pipeline_approved_from_response(
        self, mock_invoke, chain_result_approved, valid_pa_request, full_phi_pa_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        pipeline = PriorAuthPipeline(
            rule_validators=[],
            ai_interpreter=interpreter,
        )
        result = pipeline.run(valid_pa_request, response=full_phi_pa_response)

        assert result.approved is True
        assert result.response is full_phi_pa_response


# ---------------------------------------------------------------------------
# TestSkipAI
# ---------------------------------------------------------------------------


class TestSkipAI:
    """Test AI phase skipping behavior."""

    def test_no_ai_config_no_summary(self, valid_pa_request):
        pipeline = PriorAuthPipeline(rule_validators=[])
        result = pipeline.run(valid_pa_request)

        assert result.ai_summary is None

    def test_no_response_no_ai(self, valid_pa_request):
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        pipeline = PriorAuthPipeline(
            rule_validators=[],
            ai_interpreter=interpreter,
        )
        result = pipeline.run(valid_pa_request)

        assert result.ai_summary is None

    def test_skip_ai_true_no_ai(self, valid_pa_request, full_phi_pa_response):
        settings = ClaimValidatorSettings(
            pa_rule_validators=[],
            pa_skip_ai=True,
        )
        pipeline = PriorAuthPipeline.from_settings(settings)
        result = pipeline.run(valid_pa_request, response=full_phi_pa_response)

        assert result.ai_summary is None


# ---------------------------------------------------------------------------
# TestFindingConventions
# ---------------------------------------------------------------------------


class TestFindingConventions:
    """Test finding code and severity conventions."""

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_all_findings_ai_pa_prefix(
        self, mock_invoke, chain_result_approved, deidentified_approved_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        _, findings = interpreter.interpret(deidentified_approved_response)

        for f in findings:
            assert f.code.startswith("AI_PA_"), f"Finding {f.code} missing prefix"

    @patch("claim_validator.chains.base.invoke_chain_safe")
    def test_all_findings_warning_severity(
        self, mock_invoke, chain_result_approved, deidentified_approved_response,
    ):
        mock_invoke.return_value = chain_result_approved
        mock_chain = MagicMock()
        interpreter = PriorAuthInterpreterAI(chain=mock_chain)
        _, findings = interpreter.interpret(deidentified_approved_response)

        for f in findings:
            assert f.severity == Severity.WARNING


# ---------------------------------------------------------------------------
# TestImports
# ---------------------------------------------------------------------------


class TestImports:
    """Test that AI interpreter is importable from expected locations."""

    def test_import_from_prior_auth_module(self):
        from claim_validator.prior_auth import PriorAuthInterpreterAI

        assert PriorAuthInterpreterAI is not None

    def test_import_from_validators_ai(self):
        from claim_validator.prior_auth.validators.ai import (
            PriorAuthInterpreterAI,
        )

        assert PriorAuthInterpreterAI is not None

    def test_import_from_interpreter_module(self):
        from claim_validator.prior_auth.validators.ai.interpreter import (
            PriorAuthInterpreterAI,
        )

        assert PriorAuthInterpreterAI is not None

    def test_import_from_top_level(self):
        from claim_validator import PriorAuthInterpreterAI

        assert PriorAuthInterpreterAI is not None
