"""FastAPI security middleware — enforces auth on all endpoints.

Usage:
    app = FastAPI()
    app.add_middleware(AuthMiddleware)

    # Or protect specific routes:
    @app.get("/protected", dependencies=[Depends(require_auth)])
    def protected():
        ...
"""
from __future__ import annotations

import logging
from typing import Optional

from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from greymatter_core.security.tokens import verify_node_token

logger = logging.getLogger(__name__)

_bearer_scheme = HTTPBearer(auto_error=False)

# Paths that don't require auth (health checks for load balancers)
PUBLIC_PATHS = frozenset({
    "/health",
    "/docs",
    "/openapi.json",
    "/redoc",
})


async def require_auth(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer_scheme),
) -> str:
    """Dependency that requires valid bearer token auth.

    Returns the authenticated node_id.

    Raises:
        HTTPException 401 if no token or invalid token.
        HTTPException 403 if token expired.
    """
    if credentials is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing authorization header",
            headers={"WWW-Authenticate": "Bearer"},
        )

    valid, node_id = verify_node_token(credentials.credentials)
    if not valid:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # Attach node_id to request state for downstream use
    request.state.node_id = node_id
    return node_id


class AuthMiddleware:
    """ASGI middleware that enforces auth on all non-public paths.

    Use this for blanket protection. Individual route protection
    via Depends(require_auth) gives more control.
    """

    def __init__(self, app, public_paths: Optional[set] = None):
        self.app = app
        self.public_paths = public_paths or PUBLIC_PATHS

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            return await self.app(scope, receive, send)

        path = scope.get("path", "")

        # Allow public paths
        if path in self.public_paths:
            return await self.app(scope, receive, send)

        # Check Authorization header
        headers = dict(scope.get("headers", []))
        auth_header = headers.get(b"authorization", b"").decode()

        if not auth_header.startswith("Bearer "):
            from starlette.responses import JSONResponse
            response = JSONResponse(
                {"detail": "Missing authorization header"},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )
            return await response(scope, receive, send)

        token = auth_header[7:]  # Strip "Bearer "
        valid, node_id = verify_node_token(token)

        if not valid:
            from starlette.responses import JSONResponse
            response = JSONResponse(
                {"detail": "Invalid or expired token"},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )
            return await response(scope, receive, send)

        # Inject node_id into scope for downstream access
        scope.setdefault("state", {})["node_id"] = node_id
        logger.debug("Authenticated request from node %s to %s", node_id, path)

        return await self.app(scope, receive, send)
