"""
MCP Exception Classes

Custom exceptions for MCP client operations with detailed error context.
"""

from typing import Any


class MCPError(Exception):
    """Base exception for all MCP-related errors."""

    def __init__(
        self,
        message: str,
        *,
        server_name: str | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message)
        self.message = message
        self.server_name = server_name
        self.details = details or {}

    def __str__(self) -> str:
        parts = [self.message]
        if self.server_name:
            parts.append(f"server={self.server_name}")
        if self.details:
            parts.append(f"details={self.details}")
        return " | ".join(parts)


class MCPConnectionError(MCPError):
    """Raised when connection to an MCP server fails."""

    def __init__(
        self,
        message: str,
        *,
        server_name: str | None = None,
        url: str | None = None,
        cause: Exception | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message, server_name=server_name, details=details)
        self.url = url
        self.cause = cause

    def __str__(self) -> str:
        base = super().__str__()
        if self.url:
            base = f"{base} | url={self.url}"
        if self.cause:
            base = f"{base} | cause={type(self.cause).__name__}: {self.cause}"
        return base


class MCPTimeoutError(MCPError):
    """Raised when an MCP operation times out."""

    def __init__(
        self,
        message: str,
        *,
        server_name: str | None = None,
        timeout_seconds: float | None = None,
        operation: str | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message, server_name=server_name, details=details)
        self.timeout_seconds = timeout_seconds
        self.operation = operation

    def __str__(self) -> str:
        base = super().__str__()
        if self.timeout_seconds is not None:
            base = f"{base} | timeout={self.timeout_seconds}s"
        if self.operation:
            base = f"{base} | operation={self.operation}"
        return base


class MCPToolError(MCPError):
    """Raised when a tool execution fails."""

    def __init__(
        self,
        message: str,
        *,
        server_name: str | None = None,
        tool_name: str | None = None,
        tool_args: dict[str, Any] | None = None,
        error_code: str | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message, server_name=server_name, details=details)
        self.tool_name = tool_name
        self.tool_args = tool_args
        self.error_code = error_code

    def __str__(self) -> str:
        base = super().__str__()
        if self.tool_name:
            base = f"{base} | tool={self.tool_name}"
        if self.error_code:
            base = f"{base} | error_code={self.error_code}"
        return base


class MCPServerNotFoundError(MCPError):
    """Raised when a requested MCP server is not registered."""

    def __init__(
        self,
        server_name: str,
        *,
        available_servers: list[str] | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        message = f"MCP server not found: {server_name}"
        super().__init__(message, server_name=server_name, details=details)
        self.available_servers = available_servers or []

    def __str__(self) -> str:
        base = super().__str__()
        if self.available_servers:
            base = f"{base} | available={self.available_servers}"
        return base


class MCPToolNotFoundError(MCPError):
    """Raised when a requested tool is not found on any server."""

    def __init__(
        self,
        tool_name: str,
        *,
        server_name: str | None = None,
        available_tools: list[str] | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        message = f"Tool not found: {tool_name}"
        super().__init__(message, server_name=server_name, details=details)
        self.tool_name = tool_name
        self.available_tools = available_tools or []

    def __str__(self) -> str:
        base = super().__str__()
        if self.available_tools:
            base = f"{base} | available_tools={self.available_tools[:5]}..."
        return base


class MCPCircuitOpenError(MCPError):
    """Raised when a circuit breaker is open (server temporarily unavailable)."""

    def __init__(
        self,
        server_name: str,
        *,
        failure_count: int | None = None,
        reset_timeout: float | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        message = f"Circuit breaker open for server: {server_name}"
        super().__init__(message, server_name=server_name, details=details)
        self.failure_count = failure_count
        self.reset_timeout = reset_timeout

    def __str__(self) -> str:
        base = super().__str__()
        if self.failure_count is not None:
            base = f"{base} | failures={self.failure_count}"
        if self.reset_timeout is not None:
            base = f"{base} | reset_in={self.reset_timeout}s"
        return base


class MCPValidationError(MCPError):
    """Raised when tool arguments fail validation."""

    def __init__(
        self,
        message: str,
        *,
        tool_name: str | None = None,
        field_errors: dict[str, str] | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message, details=details)
        self.tool_name = tool_name
        self.field_errors = field_errors or {}

    def __str__(self) -> str:
        base = super().__str__()
        if self.tool_name:
            base = f"{base} | tool={self.tool_name}"
        if self.field_errors:
            base = f"{base} | fields={list(self.field_errors.keys())}"
        return base
