from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db.engine import AsyncSessionLocal
from jose import jwt, JWTError
from app.core.config import settings
from app.constants import error_codes

security = HTTPBearer(auto_error=False)


async def get_db():
    async with AsyncSessionLocal() as session:
        yield session


async def decode_staff_token(token: str, db: AsyncSession) -> dict:
    """Shared identity check behind get_current_staff below — factored out so the
    WebSocket route in sessions.py can run the same checks on a token it receives as its
    first message, since a browser WebSocket handshake can't carry an Authorization
    header the way a normal request does."""
    try:
        payload = jwt.decode(
            token,
            settings.JWT_SECRET,
            algorithms=["HS256"],
            options={"verify_exp": False},
        )
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"error": error_codes.INVALID_TOKEN, "message": "Invalid or expired token"},
        )

    # Re-check DB on every request so deactivated/locked accounts lose access immediately
    from app.models.user import User
    result = await db.execute(select(User).where(User.id == payload["sub"]))
    user = result.scalar_one_or_none()

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"error": error_codes.INVALID_TOKEN, "message": "Invalid or expired token"},
        )

    if not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail={"error": error_codes.ACCOUNT_INACTIVE, "message": "Account is deactivated"},
        )

    if user.locked_at is not None:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail={"error": error_codes.ACCOUNT_LOCKED, "message": "Account locked. Contact admin."},
        )

    return payload


async def get_current_staff(
    credentials: HTTPAuthorizationCredentials | None = Depends(security),
    db: AsyncSession = Depends(get_db),
):
    if not credentials:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"error": error_codes.UNAUTHORIZED, "message": "Authentication required"},
        )
    return await decode_staff_token(credentials.credentials, db)
