"""Password hashing, JWT issuing/validation, and role-based access dependencies.

Kept free of any `crud` import so `crud` can import the hashing helpers without a
circular dependency. The auth dependency queries the AdminUser model directly.
"""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Annotated

import bcrypt
import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jwt.exceptions import InvalidTokenError
from sqlalchemy import select
from sqlalchemy.orm import Session

from . import models
from .config import get_settings
from .database import get_db

# --- Roles ----------------------------------------------------------------
ROLE_SUPERADMIN = "superadmin"
ROLE_ADMIN = "admin"
ROLE_VIEWER = "viewer"

# Anyone authenticated (read access to submissions).
VIEW_ROLES = (ROLE_SUPERADMIN, ROLE_ADMIN, ROLE_VIEWER)
# Can delete submissions.
MANAGE_ROLES = (ROLE_SUPERADMIN, ROLE_ADMIN)
# Can manage admin accounts.
SUPERADMIN_ONLY = (ROLE_SUPERADMIN,)

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/yatra/admin/login")


# --- Passwords ------------------------------------------------------------

def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def verify_password(password: str, hashed: str) -> bool:
    try:
        return bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
    except ValueError:
        return False


# --- JWT ------------------------------------------------------------------

def create_access_token(*, subject: str, role: str) -> str:
    settings = get_settings()
    expire = datetime.now(timezone.utc) + timedelta(
        minutes=settings.access_token_expire_minutes
    )
    payload = {"sub": subject, "role": role, "exp": expire}
    return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)


def get_current_admin(
    token: Annotated[str, Depends(oauth2_scheme)],
    db: Annotated[Session, Depends(get_db)],
) -> models.AdminUser:
    settings = get_settings()
    credentials_exc = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(
            token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]
        )
        email = payload.get("sub")
        if not email:
            raise credentials_exc
    except InvalidTokenError:
        raise credentials_exc

    admin = db.scalars(
        select(models.AdminUser).where(models.AdminUser.email == email)
    ).first()
    if admin is None or not admin.is_active:
        raise credentials_exc
    return admin


def require_roles(*allowed_roles: str):
    """Dependency factory: allow only the given roles, else 403."""

    def dependency(
        current: Annotated[models.AdminUser, Depends(get_current_admin)],
    ) -> models.AdminUser:
        if current.role not in allowed_roles:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient permissions for this action",
            )
        return current

    return dependency
