import os
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, Depends, Header, HTTPException, Request
from sqlmodel import Session, select

from app.db import get_session
from app.models import ApiSession, PasswordResetToken, UserAccount
from app.schemas import AuthToken, PasswordResetConfirm, PasswordResetRequest, UserCreate, UserLogin, UserRead
from app.services.email import send_password_reset_email
from app.services.security import generate_public_token, hash_password, reset_token_expiry, token_hash, utcnow, verify_password

router = APIRouter(prefix="/auth", tags=["auth"])


def serialize_user(user: UserAccount) -> UserRead:
    return UserRead(
        id=user.id,
        email=user.email,
        full_name=user.full_name,
        role=user.role,
        is_active=user.is_active,
        created_at=user.created_at,
    )


def get_current_user(
    authorization: Optional[str] = Header(default=None),
    session: Session = Depends(get_session),
) -> UserAccount:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="Authentification requise")

    token = authorization.split(" ", 1)[1].strip()
    api_session = session.exec(
        select(ApiSession).where(ApiSession.token_hash == token_hash(token), ApiSession.revoked_at == None)
    ).first()
    if not api_session:
        raise HTTPException(status_code=401, detail="Session invalide")
    if api_session.expires_at and api_session.expires_at < utcnow():
        raise HTTPException(status_code=401, detail="Session expirée")

    user = session.get(UserAccount, api_session.user_id)
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="Utilisateur inactif")
    return user


@router.post("/register", response_model=AuthToken)
def register(payload: UserCreate, request: Request, session: Session = Depends(get_session)):
    email = payload.email.strip().lower()
    if len(payload.password) < 8:
        raise HTTPException(status_code=400, detail="Mot de passe trop court")
    existing = session.exec(select(UserAccount).where(UserAccount.email == email)).first()
    if existing:
        raise HTTPException(status_code=409, detail="Email déjà utilisé")

    user = UserAccount(email=email, full_name=payload.full_name, password_hash=hash_password(payload.password))
    session.add(user)
    session.commit()
    session.refresh(user)

    token = generate_public_token()
    api_session = ApiSession(
        user_id=user.id,
        token_hash=token_hash(token),
        user_agent=request.headers.get("user-agent"),
        expires_at=utcnow() + timedelta(days=90),
    )
    session.add(api_session)
    session.commit()
    return AuthToken(access_token=token, user=serialize_user(user))


@router.post("/login", response_model=AuthToken)
def login(payload: UserLogin, request: Request, session: Session = Depends(get_session)):
    email = payload.email.strip().lower()
    user = session.exec(select(UserAccount).where(UserAccount.email == email)).first()
    if not user or not verify_password(payload.password, user.password_hash):
        raise HTTPException(status_code=401, detail="Identifiants invalides")
    if not user.is_active:
        raise HTTPException(status_code=403, detail="Compte inactif")

    user.last_login_at = utcnow()
    user.updated_at = utcnow()
    token = generate_public_token()
    api_session = ApiSession(
        user_id=user.id,
        token_hash=token_hash(token),
        device_id=payload.device_id,
        user_agent=request.headers.get("user-agent"),
        expires_at=utcnow() + timedelta(days=90),
    )
    session.add(user)
    session.add(api_session)
    session.commit()
    return AuthToken(access_token=token, user=serialize_user(user))


@router.get("/me", response_model=UserRead)
def me(current_user: UserAccount = Depends(get_current_user)):
    return serialize_user(current_user)


@router.post("/password/forgot")
def forgot_password(payload: PasswordResetRequest, session: Session = Depends(get_session)):
    email = payload.email.strip().lower()
    user = session.exec(select(UserAccount).where(UserAccount.email == email)).first()

    # Réponse volontairement neutre pour éviter de révéler l'existence d'un compte.
    if not user:
        return {"ok": True}

    public_token = generate_public_token()
    reset = PasswordResetToken(
        user_id=user.id,
        token_hash=token_hash(public_token),
        expires_at=reset_token_expiry(),
    )
    session.add(reset)
    session.commit()

    base_url = os.getenv("PASSWORD_RESET_BASE_URL", "https://example.com/reset-password")
    separator = "&" if "?" in base_url else "?"
    reset_url = "{}{}token={}".format(base_url, separator, public_token)
    send_password_reset_email(user.email, reset_url, user.full_name)
    return {"ok": True}


@router.post("/password/reset")
def reset_password(payload: PasswordResetConfirm, session: Session = Depends(get_session)):
    if len(payload.new_password) < 8:
        raise HTTPException(status_code=400, detail="Mot de passe trop court")

    reset = session.exec(
        select(PasswordResetToken).where(
            PasswordResetToken.token_hash == token_hash(payload.token),
            PasswordResetToken.used_at == None,
        )
    ).first()
    if not reset or reset.expires_at < utcnow():
        raise HTTPException(status_code=400, detail="Lien invalide ou expiré")

    user = session.get(UserAccount, reset.user_id)
    if not user:
        raise HTTPException(status_code=400, detail="Utilisateur introuvable")

    user.password_hash = hash_password(payload.new_password)
    user.updated_at = utcnow()
    reset.used_at = utcnow()
    session.add(user)
    session.add(reset)
    session.commit()
    return {"ok": True}
