from __future__ import annotations

import logging
import os
import warnings
from typing import Any

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse

logger = logging.getLogger(__name__)

DEFAULT_PUBLIC_PATHS = (
    "/health",
    "/docs",
    "/openapi.json",
    "/redoc",
)


def resolve_http_auth(config: dict[str, Any]) -> dict[str, Any]:
    """合并 bearer_token：config → env NIANNIAN_API_TOKEN。"""
    http = config.setdefault("http", {})
    auth = dict(http.get("auth") or {})
    token = auth.get("bearer_token") or os.environ.get("NIANNIAN_API_TOKEN")
    if token:
        auth["bearer_token"] = str(token).strip()
    public = auth.get("public_paths")
    if not public:
        auth["public_paths"] = list(DEFAULT_PUBLIC_PATHS)
    http["auth"] = auth
    return auth


def validate_http_auth(config: dict[str, Any], *, fail_fast: bool = False) -> None:
    auth = resolve_http_auth(config)
    if not auth.get("enabled"):
        return
    if auth.get("bearer_token"):
        return
    msg = "http.auth.enabled=true but no bearer_token (set NIANNIAN_API_TOKEN)"
    if fail_fast:
        raise ValueError(msg)
    warnings.warn(msg, stacklevel=2)
    logger.warning(msg)


def is_public_path(path: str, public_paths: list[str]) -> bool:
    for raw in public_paths:
        p = str(raw).rstrip("/") or "/"
        if path == p:
            return True
        if path.startswith(p + "/"):
            return True
    return False


class BearerAuthMiddleware(BaseHTTPMiddleware):
    def __init__(
        self,
        app: Any,
        *,
        enabled: bool,
        bearer_token: str | None,
        public_paths: list[str] | None = None,
    ) -> None:
        super().__init__(app)
        self._enabled = enabled
        self._token = bearer_token.strip() if bearer_token else None
        self._public_paths = list(public_paths or DEFAULT_PUBLIC_PATHS)

    async def dispatch(self, request: Request, call_next: Any) -> Any:
        if not self._enabled:
            return await call_next(request)
        path = request.url.path
        if is_public_path(path, self._public_paths):
            return await call_next(request)
        if not self._token:
            return JSONResponse(
                status_code=503,
                content={"detail": {"code": "auth_misconfigured", "message": "bearer token not configured"}},
            )
        auth = request.headers.get("authorization", "")
        prefix = "Bearer "
        if auth.startswith(prefix) and auth[len(prefix) :].strip() == self._token:
            return await call_next(request)
        return JSONResponse(
            status_code=401,
            content={"detail": {"code": "unauthorized", "message": "missing or invalid bearer token"}},
        )
