"""``mgf.fastapi.exceptions`` — sibling-domain concrete exception leaves.

Per the federation rule (decision **D3** in
``mgf-common/docs/release/federation_roadmap.md``): **siblings own
their domain concretes; mgf-common owns hierarchy roots + status
leaves.** The HTTP-01 hierarchy roots (`AppError`, `HttpError`,
`HttpClientError`, `HttpServerError`) and the generic status-code
leaves (`HttpUnauthorizedError`, `HttpForbiddenError`, `HttpNotFoundError`, etc.)
stay in :mod:`mgf.common.exceptions`. Sibling-domain concretes that
inherit from them live here.

Today the only such concrete is
:class:`HmacVerificationError`. Future framework-domain leaves
(e.g. for FastAPI-specific routing-validation failures) will land
here as they're added.
"""

from __future__ import annotations

from mgf.common.exceptions import HttpUnauthorizedError

__all__ = ["HmacVerificationError"]


class HmacVerificationError(HttpUnauthorizedError):
    """A webhook HMAC signature failed verification.

    Raised by :class:`mgf.fastapi.webhooks.HmacWebhookVerifier`
    on any failure of the Svix-shape verification dance: missing
    header, unparseable timestamp, replay-window miss, signature
    mismatch.

    Inherits from :class:`mgf.common.exceptions.HttpUnauthorizedError` so
    PAPER-24's ``http_status`` resolution maps it to 401 in
    :class:`mgf.fastapi.ExceptionTranslationMiddleware` without an
    explicit ``status_map`` entry.

    Lived under :mod:`mgf.common.exceptions` through mgf-common
    v0.27.0; moved here at the v0.28 / v0.1 federation cutover
    per the rule above.
    """

    http_status: int = 401
    #: Machine-readable failure mode (EH-08) — one of: ``missing_header``,
    #: ``bad_timestamp``, ``replay_window``, ``signature_mismatch`` (from the
    #: core verifier) or ``body_too_large`` / ``body_consumed`` (from the
    #: :func:`~mgf.fastapi.webhooks.verify_request` adapter). ``None`` if
    #: constructed without one. Lets callers branch on the cause without parsing
    #: the human message.
    reason: str | None = None

    def __init__(self, *args: object, reason: str | None = None, **kwargs: object) -> None:
        super().__init__(*args, **kwargs)
        self.reason = reason
