"""FastAPI dependencies: DB session, current user (JWT via cookie or bearer), CSRF, rate limiting."""
from typing import Optional

from fastapi import Depends, Header, Request, Response, Cookie, HTTPException, status
from fastapi.security.utils import get_authorization_scheme_param
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.core.security import decode_token, rate_limit
from app.db.session import AsyncSessionLocal


async def get_db() -> AsyncSession:
    async with AsyncSessionLocal() as session:
        yield session


async def get_current_user(request: Request, response: Response,
                           authorization: Optional[str] = Header(None),
                           mailer_session: Optional[str] = Cookie(None),
                           db: AsyncSession = Depends(get_db)) -> "User":  # noqa: F821
    """Authenticate via JWT in HttpOnly cookie (web UI) or Authorization header (API/tests)."""
    from app.models import User
    token = None
    if authorization:
        scheme, param = get_authorization_scheme_param(authorization)
        if scheme.lower() == "bearer":
            token = param
    if not token and mailer_session:
        token = mailer_session

    if not token:
        raise HTTPException(status_code=401, detail="Not authenticated")

    payload = decode_token(token)
    if not payload:
        raise HTTPException(status_code=401, detail="Invalid or expired session")

    user = await db.get(User, int(payload["sub"]))
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="User not found or inactive")
    return user


async def get_optional_user(request: Request, db: AsyncSession = Depends(get_db)):
    from app.models import User
    token = request.cookies.get(settings.SESSION_COOKIE_NAME)
    authz = request.headers.get("authorization", "")
    if authz.lower().startswith("bearer "):
        token = authz[7:]
    if not token:
        return None
    payload = decode_token(token)
    if not payload:
        return None
    return await db.get(User, int(payload["sub"]))


def require_csrf(request: Request, x_csrf_token: Optional[str] = Header(None)) -> None:
    """Double-submit cookie CSRF check for state-changing API calls from the browser."""
    if request.method in ("GET", "HEAD", "OPTIONS"):
        return
    # Exempt token-based API access (Authorization header present) and OAuth callback.
    if request.headers.get("authorization", "").lower().startswith("bearer "):
        return
    if request.url.path.startswith("/auth/google/callback"):
        return
    cookie = request.cookies.get(settings.CSRF_COOKIE_NAME, "")
    if not cookie or not x_csrf_token or cookie != x_csrf_token:
        raise HTTPException(status_code=403, detail="CSRF token missing or invalid")


def rate_limit_user(request: Request, user=Depends(get_current_user)):
    key = f"u:{user.id}:{request.url.path.split('?')[0]}"
    allowed, remaining = rate_limit(key)
    if not allowed:
        raise HTTPException(status_code=429, detail="Rate limit exceeded; try again shortly")
    return True
