import hashlib
import logging
from datetime import datetime
from typing import Optional

from core.auth import AccessTokenError, decode_access_token
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from schemas.auth import UserResponse

logger = logging.getLogger(__name__)

bearer_scheme = HTTPBearer(auto_error=False)


async def get_bearer_token(
    request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(bearer_scheme)
) -> str:
    """Extract bearer token from Authorization header."""
    if credentials and credentials.scheme.lower() == "bearer":
        return credentials.credentials

    logger.debug("Authentication required for request %s %s", request.method, request.url.path)
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication credentials were not provided")


async def get_current_user(token: str = Depends(get_bearer_token)) -> UserResponse:
    """Dependency to get current authenticated user via JWT token."""
    try:
        payload = decode_access_token(token)
    except AccessTokenError as exc:
        # Log error type only, not the full exception which may contain sensitive token data
        logger.warning("Token validation failed: %s", type(exc).__name__)
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=exc.message)

    user_id = payload.get("sub")
    if not user_id:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication token")

    last_login_raw = payload.get("last_login")
    last_login = None
    if isinstance(last_login_raw, str):
        try:
            last_login = datetime.fromisoformat(last_login_raw)
        except ValueError:
            # Log user hash instead of actual user ID to avoid exposing sensitive information
            user_hash = hashlib.sha256(str(user_id).encode()).hexdigest()[:8] if user_id else "unknown"
            logger.debug("Failed to parse last_login for user hash: %s", user_hash)

    user = UserResponse(
        id=user_id,
        email=payload.get("email", ""),
        name=payload.get("name"),
        role=payload.get("role", "user"),
        last_login=last_login,
    )

    # Tag subsequent WHMCS calls in this request with the authenticated user
    # email so whmcs_api_logs rows carry a user attribution. Best-effort only.
    try:
        from services.whmcs import set_current_user_email  # lazy to avoid cycles

        set_current_user_email(user.email or None)
    except Exception:  # noqa: BLE001
        logger.debug("Failed to set WHMCS current user email context")

    return user


async def get_admin_user(current_user: UserResponse = Depends(get_current_user)) -> UserResponse:
    """Dependency to ensure current user has admin role."""
    if current_user.role != "admin":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
    return current_user


async def get_active_user(current_user: UserResponse = Depends(get_current_user)) -> UserResponse:
    """Optionally reject tokens belonging to disabled accounts.

    The user's ``is_active`` flag lives on the DB row, not the JWT, so this
    does a light DB lookup. Only call this on sensitive non-admin routes; the
    admin dependency already implies an active session via role checks at
    login time.
    """
    # Lazy import to avoid cycles with services/models at module load.
    from core.database import db_manager
    from models.auth import LocalAuthProfile
    from sqlalchemy import select

    if db_manager is None or db_manager.async_session_maker is None:
        return current_user
    async with db_manager.async_session_maker() as session:
        result = await session.execute(
            select(LocalAuthProfile).where(LocalAuthProfile.user_id == current_user.id)
        )
        profile = result.scalar_one_or_none()
    if profile is not None and profile.is_active is False:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account is disabled")
    return current_user
