"""Custom exceptions for Celeste."""

from typing import Any


class Error(Exception):
    """Base exception for all Celeste errors."""

    pass


class ModelError(Error):
    """Errors related to model operations and registry."""

    pass


class ModelNotFoundError(ModelError):
    """Raised when a requested model cannot be found."""

    def __init__(
        self,
        model_id: str | None = None,
        provider: str | None = None,
        capability: str | None = None,
        modality: str | None = None,
    ) -> None:
        """Initialize with model details.

        Args:
            model_id: Optional specific model ID that was not found.
            provider: Optional provider name.
            capability: Optional capability name (used when no specific model_id).
            modality: Optional modality name (used when no specific model_id).
        """
        self.model_id = model_id
        self.provider = provider
        self.capability = capability
        self.modality = modality

        # Generate appropriate error message based on available parameters
        if model_id and provider:
            msg = f"Model '{model_id}' not found for provider {provider}"
        elif modality and provider:
            msg = f"No model found for modality '{modality}' with provider {provider}"
        elif modality:
            msg = f"No model found for modality '{modality}'"
        elif capability and provider:
            msg = (
                f"No model found for capability '{capability}' with provider {provider}"
            )
        elif capability:
            msg = f"No model found for capability '{capability}'"
        else:
            msg = "Model not found"

        super().__init__(msg)


class CapabilityError(Error):
    """Errors related to capability compatibility."""

    pass


class UnsupportedCapabilityError(CapabilityError):
    """Raised when a model doesn't support a requested capability."""

    def __init__(self, model_id: str, capability: str) -> None:
        """Initialize with model and capability details."""
        self.model_id = model_id
        self.capability = capability
        super().__init__(
            f"Model '{model_id}' does not support capability '{capability}'"
        )


class ClientError(Error):
    """Errors related to client operations."""

    pass


class ClientNotFoundError(ClientError):
    """Raised when no client is registered for a capability/modality/provider combination."""

    def __init__(
        self,
        capability: str | None = None,
        provider: str | None = None,
        modality: str | None = None,
        operation: str | None = None,
    ) -> None:
        """Initialize with capability/modality and provider details."""
        self.capability = capability
        self.provider = provider
        self.modality = modality
        self.operation = operation

        # Build message based on available parameters
        if modality and operation and provider:
            msg = f"No client registered for modality '{modality}', operation '{operation}' with provider {provider}"
        elif modality and provider:
            msg = f"No client registered for modality '{modality}' with provider {provider}"
        elif modality:
            msg = f"No client registered for modality '{modality}'"
        elif capability and provider:
            msg = f"No client registered for {capability} with provider {provider}"
        else:
            msg = "No client registered"

        super().__init__(msg)


class ModalityNotFoundError(ClientError):
    """Raised when no modality client is registered for a modality/provider combination."""

    def __init__(self, modality: str, provider: str | None = None) -> None:
        """Initialize with modality and provider details."""
        self.modality = modality
        self.provider = provider
        if provider:
            super().__init__(
                f"No client registered for modality '{modality}' with provider {provider}"
            )
        else:
            super().__init__(f"No client registered for modality '{modality}'")


class StreamingError(Error):
    """Errors related to streaming operations."""

    pass


class ValidationError(Error):
    """Errors related to parameter and constraint validation."""

    pass


class ConstraintViolationError(ValidationError):
    """Raised when a value violates a constraint."""

    pass


class StreamingNotSupportedError(StreamingError):
    """Raised when streaming is requested for a model that doesn't support it."""

    def __init__(self, model_id: str) -> None:
        """Initialize with model ID."""
        self.model_id = model_id
        super().__init__(f"Streaming not supported for model '{model_id}'")


class StreamNotExhaustedError(StreamingError):
    """Raised when accessing stream output before consuming all chunks."""

    def __init__(self) -> None:
        """Initialize with helpful message."""
        super().__init__(
            "Stream not exhausted. Consume all chunks before accessing .output"
        )


class StreamEmptyError(StreamingError):
    """Raised when a stream completes without producing any chunks."""

    def __init__(self) -> None:
        """Initialize with helpful message."""
        super().__init__("Stream completed but no chunks were produced")


class StreamEventError(StreamingError):
    """Raised when the provider sends an error event during streaming."""

    def __init__(
        self,
        message: str,
        *,
        error_type: str | None = None,
        event_data: dict[str, Any] | None = None,
        provider: str | None = None,
    ) -> None:
        """Initialize with error details from the stream event."""
        self.error_type = error_type
        self.event_data = event_data or {}
        self.provider = provider

        prefix = f"{provider} stream error" if provider else "Stream error"
        suffix = f" [{error_type}]" if error_type else ""
        super().__init__(f"{prefix}{suffix}: {message}")


class MissingDependencyError(Error):
    """Raised when a required optional dependency is not installed."""

    def __init__(self, *, library: str, extra: str) -> None:
        super().__init__(
            f"Missing required dependency `{library}`. "
            f'Install it with: pip install "celeste-ai[{extra}]"'
        )


class CredentialsError(Error):
    """Errors related to API credentials."""

    pass


class MissingCredentialsError(CredentialsError):
    """Raised when required credentials are not configured."""

    def __init__(self, provider: str) -> None:
        """Initialize with provider details."""
        self.provider = provider
        super().__init__(
            f"Provider {provider} has no credentials configured. "
            f"Set the appropriate environment variable or pass api_key parameter."
        )


class UnsupportedProviderError(CredentialsError):
    """Raised when a provider is not configured in the credential system."""

    def __init__(self, provider: str) -> None:
        """Initialize with provider details."""
        self.provider = provider
        super().__init__(
            f"Provider {provider} has no credential mapping. "
            f"This provider is not configured in the credential system."
        )


class InvalidToolError(ValidationError):
    """Raised when a tool item is not a Tool instance or dict."""

    def __init__(self, item: object) -> None:
        """Initialize with the invalid item."""
        self.item = item
        super().__init__(f"Expected Tool instance or dict, got {type(item).__name__}")


class UnsupportedParameterError(ValidationError):
    """Raised when a parameter is not supported by a model."""

    def __init__(self, parameter: str, model_id: str) -> None:
        """Initialize with parameter and model details."""
        self.parameter = parameter
        self.model_id = model_id
        super().__init__(
            f"Parameter '{parameter}' is not supported by model '{model_id}'"
        )


class UnsupportedParameterWarning(UserWarning):
    """Emitted when a parameter is not supported by a provider and will be ignored."""


__all__ = [
    "ClientNotFoundError",
    "ConstraintViolationError",
    "Error",
    "InvalidToolError",
    "MissingCredentialsError",
    "MissingDependencyError",
    "ModalityNotFoundError",
    "ModelNotFoundError",
    "StreamEmptyError",
    "StreamEventError",
    "StreamNotExhaustedError",
    "StreamingNotSupportedError",
    "UnsupportedCapabilityError",
    "UnsupportedParameterError",
    "UnsupportedParameterWarning",
    "UnsupportedProviderError",
]
