"""
Gestion de l'authentification, des sessions sécurisées et de la protection anti-CSRF pour Versailles Airsoft.
Hachage des mots de passe avec bcrypt, sessions signées et tokens CSRF avec itsdangerous.
"""

import os
import secrets
import time
from typing import Optional
import bcrypt
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
from fastapi import Request, Response, Depends, HTTPException, status
from sqlalchemy.orm import Session

from database import get_db
from models import User

# Clé secrète issue des variables d'environnement
SECRET_KEY = os.getenv("SECRET_KEY", "versailles_default_secret_key_tactical_2026")
COOKIE_NAME = "airsoft_session"

# Durées de vie des sessions (en secondes)
SESSION_DEFAULT_MAX_AGE = 60 * 60 * 24        # 24 heures (session standard)
SESSION_REMEMBER_MAX_AGE = 60 * 60 * 24 * 30  # 30 jours (session longue durée)
SESSION_MAX_AGE = SESSION_REMEMBER_MAX_AGE     # Rétrocompatibilité
CSRF_MAX_AGE = 60 * 60 * 24                    # 24 heures

session_serializer = URLSafeTimedSerializer(SECRET_KEY, salt="versailles-session")
csrf_serializer = URLSafeTimedSerializer(SECRET_KEY, salt="versailles-csrf")

# Rétrocompatibilité pour les imports existants
serializer = session_serializer


def hash_password(password: str) -> str:
    """Hache un mot de passe en clair à l'aide de bcrypt."""
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
    return hashed.decode("utf-8")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Vérifie si le mot de passe correspond au hash stocké."""
    try:
        return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
    except Exception:
        return False


def create_session_token(user_id: int, username: str, remember_me: bool = False) -> str:
    """Génère un token de session chiffré/signé."""
    payload = {
        "user_id": user_id,
        "username": username,
        "remember": remember_me,
        "created_at": time.time()
    }
    return session_serializer.dumps(payload)


def verify_session_token(token: str, max_age: Optional[int] = None) -> Optional[dict]:
    """Décode et valide le token de session avec vérification de la signature et expiration."""
    if not token or not isinstance(token, str):
        return None
    try:
        effective_max_age = max_age if max_age is not None else SESSION_REMEMBER_MAX_AGE
        data = session_serializer.loads(token, max_age=effective_max_age)
        if isinstance(data, dict) and "user_id" in data:
            is_remember = data.get("remember", True)
            token_created = data.get("created_at")
            if not is_remember and token_created:
                if (time.time() - token_created) > SESSION_DEFAULT_MAX_AGE:
                    return None
            return data
        return None
    except (BadSignature, SignatureExpired, Exception):
        return None


def is_secure_request(request: Request) -> bool:
    """
    Détermine si les cookies doivent être sécurisés (Secure=True).
    Le flag Secure est activé si :
    1. L'environnement n'est pas 'development' (production, staging, etc.)
    2. La requête arrive en HTTPS directe
    3. L'en-tête X-Forwarded-Proto indique HTTPS (reverse proxy, Cloudflare, Traefik, Nginx)
    """
    env = os.getenv("ENVIRONMENT", os.getenv("ENV", "development")).strip().lower()
    if env not in ("development", "dev", "test", "testing"):
        return True

    if request.url.scheme == "https":
        return True

    x_forwarded_proto = request.headers.get("x-forwarded-proto", "").strip().lower()
    if x_forwarded_proto == "https":
        return True

    return False


def set_session_cookie(
    response: Response,
    token: str,
    request: Request,
    remember_me: bool = False
) -> None:
    """Configure le cookie de session sécurisé avec les attributs HttpOnly, SameSite et Secure dynamique."""
    max_age = SESSION_REMEMBER_MAX_AGE if remember_me else SESSION_DEFAULT_MAX_AGE
    secure = is_secure_request(request)
    response.set_cookie(
        key=COOKIE_NAME,
        value=token,
        max_age=max_age,
        httponly=True,
        samesite="lax",
        secure=secure,
        path="/"
    )


def delete_session_cookie(response: Response) -> None:
    """Supprime le cookie de session."""
    response.delete_cookie(
        key=COOKIE_NAME,
        path="/",
        httponly=True,
        samesite="lax"
    )


# ==========================================
# PROTECTION ANTI-CSRF (CROSS-SITE REQUEST FORGERY)
# ==========================================

def generate_csrf_token() -> str:
    """Génère un jeton CSRF cryptographique signé et temporisé."""
    payload = {
        "nonce": secrets.token_hex(16),
        "ts": time.time()
    }
    return csrf_serializer.dumps(payload)


def verify_csrf_token(token: Optional[str]) -> bool:
    """Valide l'authenticité et la date d'expiration d'un jeton CSRF."""
    if not token or not isinstance(token, str):
        return False
    try:
        data = csrf_serializer.loads(token, max_age=CSRF_MAX_AGE)
        return isinstance(data, dict) and "nonce" in data
    except (BadSignature, SignatureExpired, Exception):
        return False


async def validate_csrf(request: Request) -> bool:
    """
    Dépendance FastAPI pour valider le jeton CSRF sur les requêtes modifiant l'état (POST/PUT/DELETE).
    Vérifie dans l'ordre :
    1. En-têtes HTTP (X-CSRF-Token, x-csrf-token, HX-CSRF-Token)
    2. Données de formulaire (champ csrf_token ou _csrf_token)
    """
    # 1. En-tête HTTP (HTMX / AJAX / fetch)
    token = (
        request.headers.get("X-CSRF-Token")
        or request.headers.get("x-csrf-token")
        or request.headers.get("X-CSRFToken")
        or request.headers.get("HX-CSRF-Token")
    )
    if token and verify_csrf_token(token):
        return True

    # 2. Données du formulaire POST
    try:
        form = await request.form()
        form_token = form.get("csrf_token") or form.get("_csrf_token")
        if form_token and verify_csrf_token(str(form_token)):
            return True
    except Exception:
        pass

    raise HTTPException(
        status_code=status.HTTP_403_FORBIDDEN,
        detail="Jeton CSRF invalide ou expiré. Veuillez rafraîchir la page."
    )


# ==========================================
# GESTION DES UTILISATEURS & DÉPENDANCES FASTAPI
# ==========================================

def get_current_user(request: Request, db: Session = Depends(get_db)) -> Optional[User]:
    """
    Récupère l'utilisateur actuellement connecté depuis le cookie de session.
    Retourne None si l'utilisateur n'est pas connecté ou si le token est invalide.
    """
    token = request.cookies.get(COOKIE_NAME)
    if not token:
        return None

    data = verify_session_token(token)
    if not data or "user_id" not in data:
        return None

    user = db.query(User).filter(User.id == data["user_id"]).first()
    return user


def require_user(request: Request, db: Session = Depends(get_db)) -> User:
    """
    Dépendance exigeant un utilisateur connecté.
    Lève une exception 401 si non authentifié.
    """
    user = get_current_user(request, db)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Connexion requise pour effectuer cette opération"
        )
    return user
