"""
Tests for exceptions module.
"""

from exceptions import (
    AllProvidersFailedError,
    CircuitOpenError,
    ConfigurationError,
    ContextTooLongError,
    CostLimitExceededError,
    ErrorCode,
    InvalidModelError,
    InvalidModelGroupError,
    LLMGatewayError,
    ModelNotAvailableError,
    ProviderError,
    RateLimitError,
    StreamError,
    TokenLimitExceededError,
)


class TestErrorCode:
    """Tests for ErrorCode enum."""

    def test_error_code_values(self) -> None:
        """Test error code values."""
        assert ErrorCode.UNKNOWN_ERROR.value == "LLM_UNKNOWN_ERROR"
        assert ErrorCode.PROVIDER_ERROR.value == "LLM_PROVIDER_ERROR"
        assert ErrorCode.CIRCUIT_OPEN.value == "LLM_CIRCUIT_OPEN"
        assert ErrorCode.COST_LIMIT_EXCEEDED.value == "LLM_COST_LIMIT_EXCEEDED"


class TestLLMGatewayError:
    """Tests for LLMGatewayError base class."""

    def test_basic_error(self) -> None:
        """Test basic error creation."""
        error = LLMGatewayError("Something went wrong")
        assert str(error) == "[LLM_UNKNOWN_ERROR] Something went wrong"
        assert error.message == "Something went wrong"
        assert error.code == ErrorCode.UNKNOWN_ERROR
        assert error.details == {}
        assert error.cause is None

    def test_error_with_code(self) -> None:
        """Test error with custom code."""
        error = LLMGatewayError(
            "Provider failed",
            code=ErrorCode.PROVIDER_ERROR,
        )
        assert error.code == ErrorCode.PROVIDER_ERROR

    def test_error_with_details(self) -> None:
        """Test error with details."""
        error = LLMGatewayError(
            "Error",
            details={"key": "value"},
        )
        assert error.details == {"key": "value"}

    def test_error_with_cause(self) -> None:
        """Test error with cause exception."""
        cause = ValueError("Original error")
        error = LLMGatewayError("Wrapped error", cause=cause)
        assert error.cause is cause

    def test_to_dict(self) -> None:
        """Test converting error to dict."""
        error = LLMGatewayError(
            "Test error",
            code=ErrorCode.INVALID_REQUEST,
            details={"field": "value"},
        )
        result = error.to_dict()

        assert result["error"] == "LLM_INVALID_REQUEST"
        assert result["message"] == "Test error"
        assert result["details"] == {"field": "value"}

    def test_to_dict_no_details(self) -> None:
        """Test to_dict without details."""
        error = LLMGatewayError("Test error")
        result = error.to_dict()

        assert "details" not in result

    def test_repr(self) -> None:
        """Test error repr."""
        error = LLMGatewayError("Test", details={"key": "val"})
        repr_str = repr(error)

        assert "LLMGatewayError" in repr_str
        assert "Test" in repr_str


class TestProviderError:
    """Tests for ProviderError."""

    def test_basic_provider_error(self) -> None:
        """Test basic provider error."""
        error = ProviderError(
            message="API call failed",
            provider="anthropic",
        )

        assert error.provider == "anthropic"
        assert error.model is None
        assert error.status_code is None
        assert error.code == ErrorCode.PROVIDER_ERROR
        assert "provider" in error.details

    def test_provider_error_with_model(self) -> None:
        """Test provider error with model info."""
        error = ProviderError(
            message="Model not found",
            provider="openai",
            model="gpt-5",
            status_code=404,
        )

        assert error.model == "gpt-5"
        assert error.status_code == 404
        assert error.details["model"] == "gpt-5"
        assert error.details["status_code"] == 404

    def test_provider_error_with_cause(self) -> None:
        """Test provider error with cause."""
        cause = ConnectionError("Network down")
        error = ProviderError(
            message="Connection failed",
            provider="google",
            cause=cause,
        )

        assert error.cause is cause


class TestRateLimitError:
    """Tests for RateLimitError."""

    def test_internal_rate_limit(self) -> None:
        """Test internal rate limit error."""
        error = RateLimitError(
            message="Too many requests",
            retry_after=60,
        )

        assert error.code == ErrorCode.RATE_LIMIT_EXCEEDED
        assert error.provider is None
        assert error.retry_after == 60
        assert error.details["retry_after_seconds"] == 60

    def test_provider_rate_limit(self) -> None:
        """Test provider rate limit error."""
        error = RateLimitError(
            message="OpenAI rate limit",
            provider="openai",
            retry_after=30,
        )

        assert error.code == ErrorCode.PROVIDER_RATE_LIMIT
        assert error.provider == "openai"
        assert error.details["provider"] == "openai"


class TestCircuitOpenError:
    """Tests for CircuitOpenError."""

    def test_circuit_open_error(self) -> None:
        """Test circuit open error."""
        error = CircuitOpenError(
            provider="anthropic",
            recovery_time=45,
        )

        assert error.provider == "anthropic"
        assert error.recovery_time == 45
        assert error.code == ErrorCode.CIRCUIT_OPEN
        assert "Circuit breaker open" in error.message
        assert error.details["recovery_time_seconds"] == 45

    def test_circuit_open_no_recovery_time(self) -> None:
        """Test circuit open without recovery time."""
        error = CircuitOpenError(provider="openai")

        assert error.recovery_time is None
        assert "recovery_time_seconds" not in error.details


class TestCostLimitExceededError:
    """Tests for CostLimitExceededError."""

    def test_project_cost_limit(self) -> None:
        """Test project cost limit error."""
        error = CostLimitExceededError(
            entity_type="project",
            entity_id="proj-123",
            current_cost=150.0,
            limit=100.0,
        )

        assert error.entity_type == "project"
        assert error.entity_id == "proj-123"
        assert error.current_cost == 150.0
        assert error.limit == 100.0
        assert error.code == ErrorCode.COST_LIMIT_EXCEEDED
        assert "$150.00" in error.message
        assert "$100.00" in error.message

    def test_agent_cost_limit(self) -> None:
        """Test agent cost limit error."""
        error = CostLimitExceededError(
            entity_type="agent",
            entity_id="agent-456",
            current_cost=50.0,
            limit=25.0,
        )

        assert error.entity_type == "agent"
        assert error.details["entity_type"] == "agent"


class TestInvalidModelGroupError:
    """Tests for InvalidModelGroupError."""

    def test_invalid_group_error(self) -> None:
        """Test invalid model group error."""
        error = InvalidModelGroupError(
            model_group="invalid_group",
            available_groups=["reasoning", "code", "fast"],
        )

        assert error.model_group == "invalid_group"
        assert error.available_groups == ["reasoning", "code", "fast"]
        assert error.code == ErrorCode.INVALID_MODEL_GROUP
        assert "invalid_group" in error.message

    def test_invalid_group_no_available(self) -> None:
        """Test invalid group without available list."""
        error = InvalidModelGroupError(model_group="unknown")

        assert error.available_groups is None
        assert "available_groups" not in error.details


class TestInvalidModelError:
    """Tests for InvalidModelError."""

    def test_invalid_model_error(self) -> None:
        """Test invalid model error."""
        error = InvalidModelError(
            model="gpt-99",
            reason="Model does not exist",
        )

        assert error.model == "gpt-99"
        assert error.code == ErrorCode.INVALID_MODEL
        assert "gpt-99" in error.message
        assert "Model does not exist" in error.message

    def test_invalid_model_no_reason(self) -> None:
        """Test invalid model without reason."""
        error = InvalidModelError(model="unknown-model")

        assert "reason" not in error.details


class TestModelNotAvailableError:
    """Tests for ModelNotAvailableError."""

    def test_model_not_available(self) -> None:
        """Test model not available error."""
        error = ModelNotAvailableError(
            model="claude-opus-4",
            provider="anthropic",
        )

        assert error.model == "claude-opus-4"
        assert error.provider == "anthropic"
        assert error.code == ErrorCode.MODEL_NOT_AVAILABLE
        assert "not configured" in error.message


class TestAllProvidersFailedError:
    """Tests for AllProvidersFailedError."""

    def test_all_providers_failed(self) -> None:
        """Test all providers failed error."""
        errors = [
            {"model": "claude-opus-4", "error": "Rate limited"},
            {"model": "gpt-4.1", "error": "Timeout"},
        ]
        error = AllProvidersFailedError(
            model_group="reasoning",
            attempted_models=["claude-opus-4", "gpt-4.1"],
            errors=errors,
        )

        assert error.model_group == "reasoning"
        assert error.attempted_models == ["claude-opus-4", "gpt-4.1"]
        assert error.errors == errors
        assert error.code == ErrorCode.ALL_PROVIDERS_FAILED


class TestStreamError:
    """Tests for StreamError."""

    def test_stream_error(self) -> None:
        """Test stream error."""
        cause = OSError("Connection reset")
        error = StreamError(
            message="Stream interrupted",
            chunks_received=10,
            cause=cause,
        )

        assert error.chunks_received == 10
        assert error.cause is cause
        assert error.code == ErrorCode.STREAM_ERROR


class TestTokenLimitExceededError:
    """Tests for TokenLimitExceededError."""

    def test_token_limit_exceeded(self) -> None:
        """Test token limit exceeded error."""
        error = TokenLimitExceededError(
            model="claude-haiku",
            token_count=10000,
            limit=8192,
        )

        assert error.model == "claude-haiku"
        assert error.token_count == 10000
        assert error.limit == 8192
        assert error.code == ErrorCode.TOKEN_LIMIT_EXCEEDED


class TestContextTooLongError:
    """Tests for ContextTooLongError."""

    def test_context_too_long(self) -> None:
        """Test context too long error."""
        error = ContextTooLongError(
            model="gpt-4.1-mini",
            context_length=150000,
            max_context=100000,
        )

        assert error.model == "gpt-4.1-mini"
        assert error.context_length == 150000
        assert error.max_context == 100000
        assert error.code == ErrorCode.CONTEXT_TOO_LONG


class TestConfigurationError:
    """Tests for ConfigurationError."""

    def test_configuration_error(self) -> None:
        """Test configuration error."""
        error = ConfigurationError(
            message="Missing API key",
            config_key="ANTHROPIC_API_KEY",
        )

        assert error.config_key == "ANTHROPIC_API_KEY"
        assert error.code == ErrorCode.CONFIGURATION_ERROR
        assert error.details["config_key"] == "ANTHROPIC_API_KEY"

    def test_configuration_error_no_key(self) -> None:
        """Test configuration error without key."""
        error = ConfigurationError(message="Invalid configuration")

        assert error.config_key is None
        assert "config_key" not in error.details
