"""FastAPI dependencies for authentication and authorization."""

from __future__ import annotations

import hashlib
import logging
from collections.abc import Callable
from datetime import UTC, datetime

from fastapi import HTTPException, Request

from specwright import analytics

from .jwt import resolve_org_login, validate_access_token
from .models import ANONYMOUS_USER, CurrentUser, PermissionDenied
from .permissions import ALL_PERMISSION_VALUES, Permission

logger = logging.getLogger(__name__)


async def get_current_user(request: Request) -> CurrentUser:
    """Resolve the current user from session, API key, or anonymous.

    Priority:
    1. Authorization: Bearer header (API key hash lookup or JWT)
    2. Session cookie (Auth0)
    3. Anonymous (dev mode — all permissions when auth0 is disabled)
    """
    settings = request.app.state.settings

    # 1. Check Authorization header (API key)
    auth_header = request.headers.get("Authorization", "")
    if auth_header.startswith("Bearer "):
        token = auth_header[7:]
        # API key format: sw_<base64url>
        if token.startswith("sw_"):
            return await _resolve_api_key(request, token)
        # JWT (Auth0 access token) — Phase 4
        return await _resolve_jwt(request, token)

    # 2. Session cookie
    session_user = request.session.get("user") if hasattr(request, "session") else None
    if session_user:
        return _session_to_current_user(session_user)

    # 3. Anonymous — grant all permissions when auth0 is disabled
    if not settings.auth0_enabled:
        return ANONYMOUS_USER

    analytics.track("auth_denied", properties={"reason": "unauthenticated"})
    raise HTTPException(status_code=401, detail="Authentication required")


def _session_to_current_user(session_user: dict) -> CurrentUser:
    """Convert a session dict into a CurrentUser."""
    # Parse permissions from session (set during callback)
    raw_perms = session_user.get("permissions", [])
    permissions = frozenset(Permission(p) for p in raw_perms if p in ALL_PERMISSION_VALUES)
    # Fallback: if no permissions stored yet (pre-migration sessions or RBAC
    # not fully wired), grant read + write.  Safe because only verified
    # Gerner-Ventures org members can authenticate via the Auth0 Action.
    if not permissions and session_user.get("sub"):
        permissions = frozenset({Permission.SPECS_READ, Permission.SPECS_WRITE})

    return CurrentUser(
        sub=session_user.get("sub", ""),
        email=session_user.get("email", ""),
        name=session_user.get("name", ""),
        picture=session_user.get("picture", ""),
        org_id=session_user.get("org_id", ""),
        org_login=session_user.get("org_login", ""),
        permissions=permissions,
        auth_method="session",
    )


async def _resolve_api_key(request: Request, token: str) -> CurrentUser:
    """Look up an API key by its SHA-256 hash and build a CurrentUser."""
    user_store = getattr(request.app.state, "user_store", None)
    if user_store is None:
        analytics.track("auth_denied", properties={"reason": "api_key_unavailable"})
        raise HTTPException(status_code=401, detail="API key auth not available")

    # Reuse the lookup from middleware if available (avoids a second DB hit)
    api_key = getattr(request.state, "_resolved_api_key", None)
    if api_key is None:
        key_hash = hashlib.sha256(token.encode()).hexdigest()
        api_key = await user_store.get_api_key_by_hash(key_hash)
    if api_key is None:
        analytics.track("auth_denied", properties={"reason": "invalid_api_key"})
        raise HTTPException(status_code=401, detail="Invalid API key")

    if api_key.get("revoked_at") is not None:
        analytics.track("auth_denied", properties={"reason": "revoked_api_key"})
        raise HTTPException(status_code=401, detail="API key has been revoked")

    # Check expiry
    expires_at = api_key.get("expires_at")
    if expires_at is not None and expires_at < datetime.now(UTC):
        analytics.track("auth_denied", properties={"reason": "expired_api_key"})
        raise HTTPException(status_code=401, detail="API key has expired")

    scopes = api_key.get("scopes", [])
    permissions = frozenset(Permission(s) for s in scopes if s in ALL_PERMISSION_VALUES)

    return CurrentUser(
        sub=api_key.get("user_sub", ""),
        email=api_key.get("user_email", ""),
        name=api_key.get("user_name", ""),
        org_id="",
        org_login=api_key.get("org_login", ""),
        permissions=permissions,
        auth_method="api_key",
    )


async def _resolve_jwt(request: Request, token: str) -> CurrentUser:
    """Validate a JWT access token and build a CurrentUser."""
    try:
        settings = request.app.state.settings
        claims = await validate_access_token(token, settings)
    except Exception as exc:
        logger.debug("JWT validation failed", exc_info=True)
        analytics.capture_exception(exc, properties={"context": "jwt_validation"})
        analytics.track("auth_denied", properties={"reason": "invalid_jwt"})
        raise HTTPException(status_code=401, detail="Invalid access token") from None

    raw_perms = claims.get("permissions", [])
    permissions = frozenset(Permission(p) for p in raw_perms if p in ALL_PERMISSION_VALUES)

    org_id = claims.get("org_id", "")
    registry = getattr(request.app.state, "registry", None)
    org_login = await resolve_org_login(claims, registry)

    return CurrentUser(
        sub=claims.get("sub", ""),
        email=claims.get("email", ""),
        name=claims.get("name", ""),
        org_id=org_id,
        org_login=org_login,
        permissions=permissions,
        auth_method="jwt",
    )


def require_permission(perm: Permission) -> Callable:
    """Factory for a FastAPI dependency that enforces a permission.

    Usage::

        @router.get("/admin")
        async def admin(user: CurrentUser = Depends(require_permission(Permission.SPECS_ADMIN))):
            ...
    """

    async def _check(request: Request) -> CurrentUser:
        user = await get_current_user(request)
        try:
            user.require_permission(perm)
        except PermissionDenied as exc:
            analytics.track(
                "auth_denied",
                distinct_id=user.sub or analytics.SERVER_ACTOR,
                properties={"reason": "permission_denied", "permission": perm.value},
            )
            raise HTTPException(status_code=403, detail=str(exc)) from None
        return user

    return _check
