# app/auth.py

from datetime import UTC, datetime, timedelta

from fastapi import Cookie, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext

from app.config import config
from app.core.logging import bind_log_context, get_logger
from app.database import db
from app.models import UserInDB


SECRET_KEY = config.SECRET_KEY or "cidti40"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60
logger = get_logger(__name__)

pwd_context = CryptContext(schemes=["pbkdf2_sha256", "bcrypt_sha256", "bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")

BCRYPT_MAX_BYTES = 72


def _is_bcrypt_hash(hashed_password: str) -> bool:
    return hashed_password.startswith(("$2a$", "$2b$", "$2y$", "$2x$"))


def verify_password(plain_password, hashed_password):
    try:
        if _is_bcrypt_hash(hashed_password) and len(plain_password.encode("utf-8")) > BCRYPT_MAX_BYTES:
            return False
        return pwd_context.verify(plain_password, hashed_password)
    except ValueError:
        return False


def hash_password(password):
    return pwd_context.hash(password)


async def authenticate_user(username: str, password: str):
    user_data = await db.users.find_one({"username": username})
    if not user_data or not verify_password(password, user_data["password"]):
        logger.warning("Authentication failed for username=%s", username)
        return False

    user_data["_id"] = str(user_data["_id"])
    bind_log_context(username=username)
    return UserInDB(**user_data)


def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.now(UTC) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


async def get_current_user(access_token: str = Cookie(default=None)):
    credentials_exception = HTTPException(
        status_code=401,
        detail="Not authenticated",
        headers={"X-Auth-Error": "auth_expired"},
    )

    if not access_token:
        raise credentials_exception

    try:
        token = access_token.replace("Bearer ", "")
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        user_data = await db.users.find_one({"username": username})

        if not user_data:
            raise credentials_exception

        user_data["_id"] = str(user_data["_id"])
        bind_log_context(username=username)
        return UserInDB(**user_data)

    except JWTError:
        logger.warning("JWT validation failed")
        raise credentials_exception


def require_role(role: str):
    def role_checker(user: UserInDB = Depends(get_current_user)):
        if user.role != role:
            raise HTTPException(status_code=403, detail="Acceso denegado")
        return user

    return role_checker
