"""Deprecation header middleware for versioned API endpoints.

Adds ``Deprecation``, ``Sunset``, and ``Link`` response headers to endpoints
that have been scheduled for removal, following RFC 8599 and the BanGUI
API_VERSIONING.md lifecycle policy.
"""

from __future__ import annotations

from datetime import datetime  # noqa: TC003  # Used in stringized type annotations (future annotations)
from typing import TYPE_CHECKING

from starlette.middleware.base import BaseHTTPMiddleware

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

    from starlette.requests import Request
    from starlette.responses import Response as StarletteResponse


class DeprecatedEndpoint:
    """Describes a deprecated API endpoint and its removal schedule."""

    __slots__ = ("path_prefix", "sunset_date", "successor_url")

    def __init__(
        self,
        path_prefix: str,
        sunset_date: datetime,
        successor_url: str | None = None,
    ) -> None:
        self.path_prefix = path_prefix
        self.sunset_date = sunset_date
        self.successor_url = successor_url


# Registry of deprecated endpoints.
# Add entries here when an endpoint is scheduled for removal.
# sunset_date must be a timezone-aware datetime in UTC.
_DEPRECATED_ENDPOINTS: list[DeprecatedEndpoint] = []


def register_deprecated_endpoint(
    path_prefix: str,
    sunset_date: datetime,
    successor_url: str | None = None,
) -> None:
    """Register a deprecated endpoint for deprecation header injection."""
    _DEPRECATED_ENDPOINTS.append(
        DeprecatedEndpoint(path_prefix, sunset_date, successor_url)
    )


def _is_deprecated(path: str) -> DeprecatedEndpoint | None:
    for endpoint in _DEPRECATED_ENDPOINTS:
        if path.startswith(endpoint.path_prefix):
            return endpoint
    return None


def _format_rfc5322(dt: datetime) -> str:
    return dt.strftime("%a, %d %b %Y %H:%M:%S GMT")


class DeprecationHeaderMiddleware(BaseHTTPMiddleware):
    """Inject deprecation headers on responses from deprecated endpoints.

    For any response from a path registered in ``_DEPRECATED_ENDPOINTS``,
    this middleware appends:

    - ``Deprecation: true``
    - ``Sunset: <RFC-5322 date>``
    - ``Link: <<successor_url>>; rel="successor-version"`` (if successor_url set)

    The middleware runs after the response is generated so it has access
    to the final status code and can choose to only add headers for 2xx
    responses (non-error responses from a deprecated endpoint are what
    clients need to be warned about).
    """

    async def dispatch(
        self,
        request: Request,
        call_next: Callable[[Request], Awaitable[StarletteResponse]],
    ) -> StarletteResponse:
        response: StarletteResponse = await call_next(request)

        # Add deprecation headers for 2xx and 3xx responses from deprecated paths.
        # Skipping 4xx/5xx avoids polluting error responses with deprecation headers.
        if response.status_code < 200 or response.status_code >= 400:
            return response

        deprecated = _is_deprecated(request.url.path)
        if deprecated is None:
            return response

        # All deprecation dates are stored in UTC.
        sunset_str = _format_rfc5322(deprecated.sunset_date)

        response.headers["Deprecation"] = "true"
        response.headers["Sunset"] = sunset_str

        if deprecated.successor_url:
            response.headers["Link"] = f'<{deprecated.successor_url}>; rel="successor-version"'

        return response
