"""Schema manager exceptions."""

from __future__ import annotations

from pydantic import SecretStr
from structlog.stdlib import BoundLogger

from ..sentry import report_exception
from ..slack.webhook import SlackWebhookClient

try:
    from dataclasses_avroschema.pydantic import AvroBaseModel
    from faststream import ExceptionMiddleware
except ImportError as e:
    raise ImportError(
        "The safir.kafka module requires the kafka extra. "
        "Install it with `pip install safir[kafka]`."
    ) from e

__all__ = [
    "FastStreamErrorHandler",
    "IncompatibleSchemaError",
    "InvalidAvroNameError",
    "InvalidMetadataError",
    "UnknownDeserializeError",
    "UnknownSchemaError",
]


class FastStreamErrorHandler:
    """Error handler for FastStream that reports errors to external services.

    Initializes and creates a `FastStream ExceptionMiddleware`_ handler that
    reports any uncaught exceptions in FastStream message handlers to any
    exception reporting services that are configured.

    * To report errors directly to Slack,
      `FastStreamErrorHandler.initialize_slack` must be called with a valid
      Slack webhook before any messages are handled.
    * To report errors to Sentry, Sentry has to be initialized before any
      messages are handled.

    .. _FastStream ExceptionMiddleware: https://faststream.ag2.ai/latest/getting-started/middlewares/exception

    Examples
    --------
    Use this class when creating a ``KafkaRouter``.

    .. code-block:: python

       from safir.kafka import FastStreamErrorHandler
       from safir.sentry import initialize_sentry

       # If a Sentry DSN is provided in an environment variable, then
       # exceptions will be reported to Sentry.
       initialize_sentry()

       error_handler = FastStreamErrorHandler()

       kafka_router = KafkaRouter(
           middlewares=[error_handler.make_middleware()],
           **kafka_params,
           logger=logger,
       )

       logger = get_logger("my-app")

       if config.slack.enabled:
           error_handler.initialize_slack("some_webhook", "my-app", logger)
    """

    def __init__(self) -> None:
        self._slack_client: SlackWebhookClient | None = None

    def initialize_slack(
        self, hook_url: str | SecretStr, application: str, logger: BoundLogger
    ) -> None:
        """Configure Slack alerting.

        Until this function is called, all Slack alerting for uncaught
        exceptions will be disabled.

        Parameters
        ----------
        hook_url
            The URL of the incoming webhook to use to publish the message.
        application
            Name of the application reporting an error.
        logger
            Logger to which to report errors sending messages to Slack.
        """
        self._slack_client = SlackWebhookClient(hook_url, application, logger)

    async def handle_error(self, exc: Exception) -> None:
        """Report an exception to Slack and/or Sentry and re-raise it.

        This will called by the FastStream framework when the middleware
        generated by `FastStreamErrorHandler.make_middleware` is added to a
        router or broker.
        """
        await report_exception(exc, slack_client=self._slack_client)
        raise exc

    def make_middleware(self) -> ExceptionMiddleware:
        """Create a FastStream Exception middleware to add to a broker."""
        return ExceptionMiddleware(handlers={Exception: self.handle_error})


class IncompatibleSchemaError(Exception):
    """A schema is incompatible with the latest version in the registry."""


class InvalidAvroNameError(Exception):
    """The decalred name or namespace for an Avro schema is not valid."""


class InvalidMetadataError(Exception):
    """The Meta inner class on a model has unexpected values in fields."""


class UnknownDeserializeError(Exception):
    """The schema registry client returns None when deserializing."""


class UnknownSchemaError(Exception):
    """A schema is not managed by the Registry, and therefore cannot be
    serialized into a native Python object.
    """

    def __init__(self, data: AvroBaseModel, subject: str) -> None:
        self.message = (
            f"Schema for model: {type(data).__name__} with subject: {subject}"
            " is not known to the manager. ``register`` must be called before"
            " you try to serialize instances of this model."
        )
        super().__init__(self.message)

    def __str__(self) -> str:
        return self.message
