"""Custom exception classes for AI agents.

This module defines a hierarchy of exceptions for different error types,
enabling consistent error handling and logging across agents.

Usage:
    from agent_factory.core.errors import AIServiceError, ToolExecutionError

    raise AIServiceError("Bedrock timeout", service="bedrock", is_transient=True)
    raise ToolExecutionError("Invalid input", tool_name="search_docs")
"""

from typing import Optional


class AgentError(Exception):
    """Base exception for all agent errors.

    Attributes:
        message: Human-readable error message
        original: Original exception that caused this error
        error_code: Optional error code for categorization
    """

    def __init__(
        self,
        message: str,
        *,
        original: Optional[Exception] = None,
        error_code: Optional[str] = None,
    ):
        super().__init__(message)
        self.message = message
        self.original = original
        self.error_code = error_code

    def __str__(self) -> str:
        if self.original:
            return f"{self.message} (caused by: {self.original})"
        return self.message


class ConfigurationError(AgentError):
    """Raised when there's a configuration problem.

    Examples:
        - Missing required environment variable
        - Invalid configuration value
        - Missing secrets
    """

    def __init__(self, message: str, **kwargs):
        super().__init__(message, error_code="configuration_error", **kwargs)


class AIServiceError(AgentError):
    """Raised when an AI service operation fails.

    Examples:
        - Bedrock API error
        - Anthropic API error
        - Model invocation failure

    Attributes:
        service: Name of the AI service (bedrock, anthropic)
        is_transient: Whether the error may succeed on retry
    """

    def __init__(
        self,
        message: str,
        *,
        service: Optional[str] = None,
        original: Optional[Exception] = None,
        is_transient: bool = False,
    ):
        super().__init__(message, original=original, error_code="ai_service_error")
        self.service = service
        self.is_transient = is_transient


class CircuitBreakerOpenError(AgentError):
    """Raised when a circuit breaker is open.

    This indicates that the service has been failing repeatedly
    and requests are being blocked to prevent cascading failures.

    Attributes:
        service: Name of the protected service
        reset_at: Timestamp when circuit will attempt recovery
    """

    def __init__(
        self,
        message: str = "Circuit breaker is open",
        *,
        service: Optional[str] = None,
        reset_at: Optional[float] = None,
    ):
        super().__init__(message, error_code="circuit_breaker_open")
        self.service = service
        self.reset_at = reset_at


class ToolExecutionError(AgentError):
    """Raised when a tool execution fails.

    Examples:
        - Tool not found
        - Tool parameter validation failure
        - Tool runtime error

    Attributes:
        tool_name: Name of the tool that failed
    """

    def __init__(
        self,
        message: str,
        *,
        tool_name: Optional[str] = None,
        original: Optional[Exception] = None,
    ):
        super().__init__(message, original=original, error_code="tool_execution_error")
        self.tool_name = tool_name


class ValidationError(AgentError):
    """Raised when input validation fails.

    Examples:
        - Invalid request body
        - Missing required fields
        - Invalid field format

    Attributes:
        field: Name of the field that failed validation
    """

    def __init__(
        self,
        message: str,
        *,
        field: Optional[str] = None,
        original: Optional[Exception] = None,
    ):
        super().__init__(message, original=original, error_code="validation_error")
        self.field = field


class ChannelError(AgentError):
    """Raised when a channel operation fails.

    Examples:
        - Slack API error
        - Teams API error
        - Message sending failure

    Attributes:
        channel_type: Type of channel (slack, teams)
        is_retryable: Whether the operation may succeed on retry
    """

    def __init__(
        self,
        message: str,
        *,
        channel_type: Optional[str] = None,
        original: Optional[Exception] = None,
        is_retryable: bool = False,
    ):
        super().__init__(message, original=original, error_code="channel_error")
        self.channel_type = channel_type
        self.is_retryable = is_retryable


class PermissionDeniedError(AgentError):
    """Raised when access is denied.

    Examples:
        - Unauthorized channel
        - Missing permissions
        - Expired access

    Attributes:
        resource: The resource being accessed
        user_id: The user who was denied
    """

    def __init__(
        self,
        message: str = "Permission denied",
        *,
        resource: Optional[str] = None,
        user_id: Optional[str] = None,
    ):
        super().__init__(message, error_code="permission_denied")
        self.resource = resource
        self.user_id = user_id


class RateLimitError(AgentError):
    """Raised when a rate limit is exceeded.

    Attributes:
        retry_after: Seconds until the rate limit resets
        limit_type: Type of limit (requests, tokens, etc.)
    """

    def __init__(
        self,
        message: str = "Rate limit exceeded",
        *,
        retry_after: Optional[int] = None,
        limit_type: Optional[str] = None,
    ):
        super().__init__(message, error_code="rate_limit_exceeded")
        self.retry_after = retry_after
        self.limit_type = limit_type


class MemoryError(AgentError):
    """Raised when memory operations fail.

    Examples:
        - Session not found
        - Memory storage failure
        - Invalid session state
    """

    def __init__(
        self,
        message: str,
        *,
        session_id: Optional[str] = None,
        original: Optional[Exception] = None,
    ):
        super().__init__(message, original=original, error_code="memory_error")
        self.session_id = session_id
