"""Unit tests for Teams Phone CLI exception hierarchy."""

import pytest

from teams_phone.constants import ExitCode
from teams_phone.exceptions import (
    AuthenticationError,
    AuthorizationError,
    ConfigurationError,
    ContractError,
    NotFoundError,
    RateLimitError,
    TeamsPhoneError,
    ValidationError,
)


class TestTeamsPhoneError:
    """Tests for the base TeamsPhoneError class."""

    @pytest.mark.unit
    def test_base_exception_exit_code(self) -> None:
        """Base exception has GENERAL_ERROR exit code."""
        exc = TeamsPhoneError("Something went wrong")
        assert exc.exit_code == ExitCode.GENERAL_ERROR
        assert exc.exit_code == 1

    @pytest.mark.unit
    def test_message_attribute(self) -> None:
        """Exception stores message as attribute."""
        message = "Something went wrong"
        exc = TeamsPhoneError(message)
        assert exc.message == message

    @pytest.mark.unit
    def test_str_without_remediation(self) -> None:
        """String representation without remediation is just the message."""
        message = "Something went wrong"
        exc = TeamsPhoneError(message)
        assert str(exc) == message

    @pytest.mark.unit
    def test_str_with_remediation(self) -> None:
        """String representation includes remediation guidance."""
        message = "Token expired"
        remediation = "Run: teams-phone auth login"
        exc = TeamsPhoneError(message, remediation=remediation)
        result = str(exc)
        assert message in result
        assert "To fix:" in result
        assert remediation in result

    @pytest.mark.unit
    def test_remediation_none_by_default(self) -> None:
        """Remediation is None when not provided."""
        exc = TeamsPhoneError("Error")
        assert exc.remediation is None

    @pytest.mark.unit
    def test_inherits_from_exception(self) -> None:
        """Base class inherits from Exception."""
        exc = TeamsPhoneError("Error")
        assert isinstance(exc, Exception)


class TestExitCodeMapping:
    """Tests verifying each exception maps to correct exit code."""

    @pytest.mark.unit
    def test_authentication_error_exit_code(self) -> None:
        """AuthenticationError maps to AUTH_ERROR (2)."""
        exc = AuthenticationError("Token expired")
        assert exc.exit_code == ExitCode.AUTH_ERROR
        assert exc.exit_code == 2

    @pytest.mark.unit
    def test_authorization_error_exit_code(self) -> None:
        """AuthorizationError maps to AUTHZ_ERROR (3)."""
        exc = AuthorizationError("Missing permissions")
        assert exc.exit_code == ExitCode.AUTHZ_ERROR
        assert exc.exit_code == 3

    @pytest.mark.unit
    def test_not_found_error_exit_code(self) -> None:
        """NotFoundError maps to NOT_FOUND (4)."""
        exc = NotFoundError("User not found")
        assert exc.exit_code == ExitCode.NOT_FOUND
        assert exc.exit_code == 4

    @pytest.mark.unit
    def test_validation_error_exit_code(self) -> None:
        """ValidationError maps to VALIDATION_ERROR (5)."""
        exc = ValidationError("Invalid phone number")
        assert exc.exit_code == ExitCode.VALIDATION_ERROR
        assert exc.exit_code == 5

    @pytest.mark.unit
    def test_rate_limit_error_exit_code(self) -> None:
        """RateLimitError maps to RATE_LIMIT (6)."""
        exc = RateLimitError("Too many requests")
        assert exc.exit_code == ExitCode.RATE_LIMIT
        assert exc.exit_code == 6

    @pytest.mark.unit
    def test_configuration_error_exit_code(self) -> None:
        """ConfigurationError maps to CONFIG_ERROR (7)."""
        exc = ConfigurationError("Invalid config")
        assert exc.exit_code == ExitCode.CONFIG_ERROR
        assert exc.exit_code == 7

    @pytest.mark.unit
    def test_contract_error_exit_code(self) -> None:
        """ContractError maps to CONTRACT_ERROR (8)."""
        exc = ContractError("Schema mismatch")
        assert exc.exit_code == ExitCode.CONTRACT_ERROR
        assert exc.exit_code == 8


class TestInheritanceHierarchy:
    """Tests verifying exception inheritance chain."""

    @pytest.mark.unit
    def test_authentication_error_inherits_from_base(self) -> None:
        """AuthenticationError inherits from TeamsPhoneError."""
        exc = AuthenticationError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_authorization_error_inherits_from_base(self) -> None:
        """AuthorizationError inherits from TeamsPhoneError."""
        exc = AuthorizationError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_not_found_error_inherits_from_base(self) -> None:
        """NotFoundError inherits from TeamsPhoneError."""
        exc = NotFoundError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_validation_error_inherits_from_base(self) -> None:
        """ValidationError inherits from TeamsPhoneError."""
        exc = ValidationError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_rate_limit_error_inherits_from_base(self) -> None:
        """RateLimitError inherits from TeamsPhoneError."""
        exc = RateLimitError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_configuration_error_inherits_from_base(self) -> None:
        """ConfigurationError inherits from TeamsPhoneError."""
        exc = ConfigurationError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)

    @pytest.mark.unit
    def test_contract_error_inherits_from_base(self) -> None:
        """ContractError inherits from TeamsPhoneError."""
        exc = ContractError("Error")
        assert isinstance(exc, TeamsPhoneError)
        assert isinstance(exc, Exception)


class TestRemediationHandling:
    """Tests for remediation message handling across exception types."""

    @pytest.mark.unit
    def test_subclass_with_remediation(self) -> None:
        """Subclass exceptions support remediation messages."""
        remediation = "Check your credentials and try again"
        exc = AuthenticationError("Invalid credentials", remediation=remediation)
        assert exc.remediation == remediation
        assert remediation in str(exc)

    @pytest.mark.unit
    def test_subclass_without_remediation(self) -> None:
        """Subclass exceptions work without remediation."""
        message = "Invalid credentials"
        exc = AuthenticationError(message)
        assert exc.remediation is None
        assert str(exc) == message

    @pytest.mark.unit
    def test_remediation_formatting(self) -> None:
        """Remediation is formatted with 'To fix:' prefix."""
        message = "Configuration file not found"
        remediation = "1. Create ~/.teams-phone/config.toml\n2. Add tenant credentials"
        exc = ConfigurationError(message, remediation=remediation)
        result = str(exc)
        expected = f"{message}\n\nTo fix:\n{remediation}"
        assert result == expected


class TestExceptionRaising:
    """Tests verifying exceptions can be raised and caught."""

    @pytest.mark.unit
    def test_raise_and_catch_base(self) -> None:
        """Base exception can be raised and caught."""
        with pytest.raises(TeamsPhoneError) as exc_info:
            raise TeamsPhoneError("Test error")
        assert str(exc_info.value) == "Test error"

    @pytest.mark.unit
    def test_raise_subclass_catch_as_base(self) -> None:
        """Subclass exception can be caught as base class."""
        with pytest.raises(TeamsPhoneError) as exc_info:
            raise AuthenticationError("Token expired")
        assert exc_info.value.exit_code == ExitCode.AUTH_ERROR

    @pytest.mark.unit
    def test_raise_subclass_catch_specific(self) -> None:
        """Subclass exception can be caught by specific type."""
        with pytest.raises(AuthenticationError) as exc_info:
            raise AuthenticationError("Token expired")
        assert exc_info.value.exit_code == ExitCode.AUTH_ERROR
