"""Integration tests for exception handling across SDK components.

Tests that exceptions are properly raised and handled by MCAClient
and other SDK components. Focuses on integration points that are
stable and verifiable without mocking complex internals.
"""

import pytest

# Test that exceptions can be imported from the SDK's public API (AC: 5)
from mca_sdk import (
    MCAClient,
    MCASDKError,
    ConfigurationError,
    RegistryError,
    RegistryConnectionError,
    RegistryConfigNotFoundError,
    RegistryAuthError,
    ValidationError,
    BufferingError,
)

from mca_sdk.config import MCAConfig


class TestPublicAPIExport:
    """Test that exceptions are exported in SDK's public API (AC: 5)."""

    def test_exceptions_importable_from_mca_sdk(self):
        """Verify exceptions can be imported from mca_sdk package."""
        # This test passing means the import above succeeded
        assert MCASDKError is not None
        assert ConfigurationError is not None
        assert RegistryError is not None
        assert ValidationError is not None
        assert BufferingError is not None

    def test_registry_sub_exceptions_importable(self):
        """Verify registry sub-exceptions are importable from mca_sdk."""
        assert RegistryConnectionError is not None
        assert RegistryConfigNotFoundError is not None
        assert RegistryAuthError is not None


class TestMCAConfigExceptions:
    """Test that MCAConfig raises appropriate exceptions (AC: 1)."""

    def test_mcaconfig_raises_configuration_error_missing_service_name(self):
        """Test MCAConfig raises ConfigurationError when service_name is missing."""
        with pytest.raises(ConfigurationError) as exc_info:
            # Don't provide service_name (required field)
            config = MCAConfig(service_name="")

        # Verify error message is contextual (AC: 4)
        error_message = str(exc_info.value)
        assert "service_name" in error_message.lower() and "required" in error_message.lower()

    def test_mcaconfig_raises_configuration_error_invalid_model_type(self):
        """Test MCAConfig raises ConfigurationError with invalid model_type."""
        with pytest.raises(ConfigurationError) as exc_info:
            config = MCAConfig(
                service_name="test-service",
                model_type="invalid-type",  # Must be internal, generative, or vendor
            )

        # Verify error message is contextual (AC: 4)
        error_message = str(exc_info.value)
        assert "model_type" in error_message.lower()
        assert "one of" in error_message.lower() or "must be" in error_message.lower()

    def test_mcaconfig_raises_configuration_error_invalid_collector_endpoint(self):
        """Test MCAConfig raises ConfigurationError with invalid collector_endpoint."""
        with pytest.raises(ConfigurationError) as exc_info:
            config = MCAConfig(
                service_name="test-service",
                collector_endpoint="ftp://invalid-protocol",  # Must be http:// or https://
            )

        # Verify error message is contextual (AC: 4)
        error_message = str(exc_info.value)
        assert "http" in error_message.lower()

    def test_catch_all_sdk_errors_from_config(self):
        """Test that MCASDKError catches all SDK exceptions from MCAConfig (AC: 5)."""
        try:
            config = MCAConfig(service_name="")  # Will raise ConfigurationError
            pytest.fail("Expected ConfigurationError to be raised")
        except MCASDKError as e:
            # Successfully caught ConfigurationError as MCASDKError
            assert isinstance(e, ConfigurationError)


class TestRegistryExceptionHierarchy:
    """Test that registry exception hierarchy works correctly (AC: 2, 5)."""

    def test_registry_connection_error_inheritance(self):
        """Test RegistryConnectionError inherits from RegistryError."""
        error = RegistryConnectionError("Connection failed")
        assert isinstance(error, RegistryError)
        assert isinstance(error, MCASDKError)

    def test_registry_config_not_found_error_inheritance(self):
        """Test RegistryConfigNotFoundError inherits from RegistryError."""
        error = RegistryConfigNotFoundError("Config not found")
        assert isinstance(error, RegistryError)
        assert isinstance(error, MCASDKError)

    def test_registry_auth_error_inheritance(self):
        """Test RegistryAuthError inherits from RegistryError."""
        error = RegistryAuthError("Auth failed")
        assert isinstance(error, RegistryError)
        assert isinstance(error, MCASDKError)

    def test_catch_registry_errors_with_base_class(self):
        """Test that RegistryError catches all registry sub-exceptions (AC: 5)."""
        # Test that RegistryError can catch sub-exceptions
        with pytest.raises(RegistryError):
            raise RegistryConnectionError("Connection failed")

        with pytest.raises(RegistryError):
            raise RegistryConfigNotFoundError("Config not found")

        with pytest.raises(RegistryError):
            raise RegistryAuthError("Auth failed")

    def test_catch_registry_errors_with_mcasdk_error(self):
        """Test that MCASDKError catches all registry exceptions (AC: 5)."""
        with pytest.raises(MCASDKError):
            raise RegistryError("Registry error")

        with pytest.raises(MCASDKError):
            raise RegistryConnectionError("Connection failed")


class TestValidationExceptionHierarchy:
    """Test that validation exception hierarchy works correctly (AC: 3, 5)."""

    def test_validation_error_inheritance(self):
        """Test ValidationError inherits from MCASDKError."""
        error = ValidationError("Validation failed")
        assert isinstance(error, MCASDKError)

    def test_catch_validation_errors_with_base_class(self):
        """Test that MCASDKError catches ValidationError (AC: 5)."""
        with pytest.raises(MCASDKError):
            raise ValidationError("Validation failed")

    def test_validation_error_has_contextual_message(self):
        """Test ValidationError supports contextual messages (AC: 4)."""
        error = ValidationError(
            "Metric name 'Invalid-Name' does not follow naming convention. "
            "Must be lowercase with underscores. Example: 'model_latency_seconds'"
        )
        message = str(error)
        assert "Invalid-Name" in message
        assert "lowercase" in message.lower() or "convention" in message.lower()
        assert "Example" in message or "example" in message.lower()


class TestBufferingExceptionHierarchy:
    """Test that buffering exception hierarchy works correctly (AC: 5)."""

    def test_buffering_error_inheritance(self):
        """Test BufferingError inherits from MCASDKError."""
        error = BufferingError("Buffering failed")
        assert isinstance(error, MCASDKError)

    def test_catch_buffering_errors_with_base_class(self):
        """Test that MCASDKError catches BufferingError (AC: 5)."""
        with pytest.raises(MCASDKError):
            raise BufferingError("Buffering failed")

    def test_buffering_error_has_contextual_message(self):
        """Test BufferingError supports contextual messages (AC: 4)."""
        error = BufferingError(
            "Telemetry buffer is full (1000/1000 items). "
            "Consider increasing buffer_size or reducing export_interval."
        )
        message = str(error)
        assert "buffer" in message.lower() or "full" in message.lower()
        assert "Consider" in message or "increasing" in message.lower()


class TestConfigurationErrorMessages:
    """Test that configuration error messages are contextual and actionable (AC: 4)."""

    def test_configuration_error_missing_service_name_message(self):
        """Test ConfigurationError for missing service_name has clear message."""
        with pytest.raises(ConfigurationError) as exc_info:
            config = MCAConfig(service_name="")

        error_message = str(exc_info.value)
        # Should clearly state what's wrong
        assert "service_name" in error_message.lower()
        assert "required" in error_message.lower()

    def test_configuration_error_invalid_model_type_message(self):
        """Test ConfigurationError for invalid model_type provides specifics."""
        with pytest.raises(ConfigurationError) as exc_info:
            config = MCAConfig(service_name="test", model_type="invalid")

        error_message = str(exc_info.value)
        # Should mention what's wrong and what's expected
        assert "model_type" in error_message.lower()
        assert any(
            word in error_message.lower()
            for word in ["one of", "must be", "internal", "generative"]
        )

    def test_configuration_error_invalid_url_message(self):
        """Test ConfigurationError for invalid URL provides guidance."""
        with pytest.raises(ConfigurationError) as exc_info:
            config = MCAConfig(service_name="test", collector_endpoint="invalid-url")

        error_message = str(exc_info.value)
        # Should mention URL requirements
        assert "http" in error_message.lower() or "protocol" in error_message.lower()


class TestExceptionSafety:
    """Test that exceptions don't leak sensitive information (HIPAA compliance)."""

    def test_exceptions_support_safe_messages(self):
        """Test that error messages can be written safely without PHI."""
        # This is a design guideline test
        # Error messages should be generic enough to not leak PHI

        # Example: field name is OK, but actual patient data should not be in error
        error = ValidationError("Attribute 'patient_id' is required but missing")
        message = str(error)

        # Should mention the field name (this is safe)
        assert "patient_id" in message.lower()
        # Message should be about the field, not contain actual PHI

    def test_registry_auth_error_doesnt_leak_tokens(self):
        """Test that RegistryAuthError can be raised without leaking tokens."""
        # Good practice: generic error message
        error = RegistryAuthError(
            "Registry authentication failed: invalid or expired token. "
            "Please refresh your authentication credentials."
        )
        message = str(error)

        # Should mention auth failure
        assert "auth" in message.lower() or "token" in message.lower()
        # Should provide guidance
        assert "refresh" in message.lower() or "credentials" in message.lower()

    def test_configuration_error_doesnt_leak_secrets(self):
        """Test that ConfigurationError can be raised without leaking secrets."""
        # Good practice: don't include actual secret values in error
        error = ConfigurationError(
            "Failed to connect with provided credentials. "
            "Verify API key is correct and not expired."
        )
        message = str(error)

        # Should mention the issue
        assert "credentials" in message.lower() or "api key" in message.lower()
        # Should NOT contain actual secret values (we design for this pattern)
