"""Tests for BaseAIValidator ABC."""

from __future__ import annotations

import abc
from typing import Any
from unittest.mock import MagicMock

import pytest

from claim_validator.constants import Severity
from claim_validator.models.deidentified import DeidentifiedClaim
from claim_validator.models.results import ValidatorOutput
from claim_validator.validators.ai.base import BaseAIValidator

# --- Helpers ---


class ConcreteAIValidator(BaseAIValidator):
    """Concrete subclass for testing."""

    name = "test_ai_validator"

    def validate_deidentified(
        self, claim: DeidentifiedClaim,
    ) -> ValidatorOutput:
        # Chain-based validators just return empty output for testing
        return self._make_output([])


class FindingAIValidator(BaseAIValidator):
    """Concrete subclass that returns findings."""

    name = "finding_ai_validator"

    def validate_deidentified(
        self, claim: DeidentifiedClaim,
    ) -> ValidatorOutput:
        return self._make_output([
            self._make_finding(
                code="AI_TEST_ISSUE",
                message="Test AI issue found",
                severity=Severity.WARNING,
                field_name="procedure_code",
            ),
        ])


def _make_deidentified_claim() -> DeidentifiedClaim:
    """Create a minimal DeidentifiedClaim for testing."""
    return DeidentifiedClaim(
        billing_provider_npi="1234567893",
        patient_age=45,
        patient_gender="M",
        payer_id="BCBS01",
        total_charge=150.0,
    )


# --- Tests ---


class TestBaseAIValidatorABC:
    """Test BaseAIValidator is a proper ABC."""

    def test_cannot_instantiate_directly(self) -> None:
        with pytest.raises(TypeError, match="abstract"):
            BaseAIValidator()  # type: ignore[abstract]

    def test_is_abstract_class(self) -> None:
        assert abc.ABC in BaseAIValidator.__mro__

    def test_validate_deidentified_is_abstract(self) -> None:
        assert getattr(
            BaseAIValidator.validate_deidentified,
            "__isabstractmethod__",
            False,
        )

    def test_extends_base_validator(self) -> None:
        from claim_validator.validators.base import BaseValidator
        assert issubclass(BaseAIValidator, BaseValidator)


class TestBaseAIValidatorConstruction:
    """Test chain/llm_client injection."""

    def test_chain_injection(self) -> None:
        mock_chain = MagicMock()
        validator = ConcreteAIValidator(chain=mock_chain)
        assert validator._chain is mock_chain
        assert validator._use_langchain is True

    def test_llm_client_injection_accepted(self) -> None:
        """llm_client param still accepted for backward compat."""
        validator = ConcreteAIValidator(llm_client="fake")
        assert validator._llm_client == "fake"

    def test_concrete_subclass_instantiates(self) -> None:
        validator = ConcreteAIValidator()
        assert validator.name == "test_ai_validator"

    def test_name_attribute_required(self) -> None:
        """Subclass without name should fail when _make_output is used."""

        class NoNameValidator(BaseAIValidator):
            def validate_deidentified(
                self, claim: DeidentifiedClaim,
            ) -> ValidatorOutput:
                return self._make_output([])

        validator = NoNameValidator()
        claim = _make_deidentified_claim()
        with pytest.raises(AttributeError):
            validator.validate_deidentified(claim)


class TestValidateRaisesNotImplemented:
    """Test that validate() raises NotImplementedError."""

    def test_validate_raises_not_implemented(self) -> None:
        from tests.conftest import build_claim_unchecked
        validator = ConcreteAIValidator()
        claim = build_claim_unchecked(
            billing_provider_npi="1234567893",
            patient_first_name="Test",
            patient_last_name="Patient",
            patient_dob="1980-01-01",
            patient_gender="M",
            subscriber_id="SUB123",
            payer_id="BCBS01",
            payer_name="BCBS",
            total_charge=150.0,
            lines=[],
            diagnosis_codes=[],
        )
        with pytest.raises(NotImplementedError, match="validate_deidentified"):
            validator.validate(claim)


class TestSendToLLMRaisesNotImplemented:
    """Test _send_to_llm raises NotImplementedError after legacy removal."""

    def test_send_to_llm_raises(self) -> None:
        validator = ConcreteAIValidator()
        with pytest.raises(NotImplementedError, match="Legacy LLM path removed"):
            validator._send_to_llm([])


class TestValidateDeidentified:
    """Test validate_deidentified with concrete subclass."""

    def setup_method(self) -> None:
        self.validator = ConcreteAIValidator()
        self.claim = _make_deidentified_claim()

    def test_returns_validator_output(self) -> None:
        result = self.validator.validate_deidentified(self.claim)
        assert isinstance(result, ValidatorOutput)

    def test_no_findings_when_clean(self) -> None:
        result = self.validator.validate_deidentified(self.claim)
        assert result.validator_name == "test_ai_validator"
        assert len(result.findings) == 0

    def test_findings_when_issue_detected(self) -> None:
        validator = FindingAIValidator()
        result = validator.validate_deidentified(self.claim)
        assert len(result.findings) == 1
        assert result.findings[0].code == "AI_TEST_ISSUE"
        assert result.findings[0].severity == Severity.WARNING

    def test_uses_make_output_helper(self) -> None:
        result = self.validator.validate_deidentified(self.claim)
        assert result.validator_name == "test_ai_validator"

    def test_uses_make_finding_helper(self) -> None:
        validator = FindingAIValidator()
        result = validator.validate_deidentified(self.claim)
        finding = result.findings[0]
        assert finding.field_name == "procedure_code"
        assert finding.suggestion == ""
