import logging
import secrets
import uuid
from datetime import datetime, timedelta, timezone

from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.core.security import (
    create_access_token,
    create_refresh_token,
    hash_password_async,
    verify_password_async,
    verify_token,
)
from app.models.utilisateur import Session, Utilisateur
from app.schemas.auth import (
    LoginRequest,
    RefreshRequest,
    SignupRequest,
    ForgotPasswordRequest,
    ResetPasswordRequest,
)
logger = logging.getLogger(__name__)


class AuthService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    # ── Sign up ───────────────────────────────────────────────────────────────

    async def signup(self, data: SignupRequest) -> Utilisateur:
        from fastapi import HTTPException

        existing = (
            await self.db.execute(
                select(Utilisateur).where(Utilisateur.email == data.email)
            )
        ).scalar_one_or_none()

        if existing:
            raise HTTPException(status_code=409, detail="Email déjà utilisé")

        hashed_pwd = await hash_password_async(data.motDePasse)
        user = Utilisateur(
            email=data.email,
            mot_de_passe_hash=hashed_pwd,
            nom=data.nom,
            prenom=data.prenom,
            type_utilisateur="UTILISATEUR",
            bureau_genre_id=data.bureauGenreId,
            telephone=data.telephone,
            filiere=data.filiere,
            campus=data.campus,
        )
        self.db.add(user)
        await self.db.flush()

        # Create default preferences
        from app.models.preference import Preference

        prefs = Preference(utilisateur_id=user.id)
        self.db.add(prefs)

        await self.db.commit()
        await self.db.refresh(user)
        return user

    # ── Login ─────────────────────────────────────────────────────────────────

    async def login(self, data: LoginRequest) -> dict:
        from fastapi import HTTPException

        user = (
            await self.db.execute(
                select(Utilisateur).where(
                    Utilisateur.email == data.email,
                    Utilisateur.est_actif == True,
                )
            )
        ).scalar_one_or_none()

        if not user or not (await verify_password_async(data.motDePasse, user.mot_de_passe_hash)):
            logging.warning(f"AUTH_FAILED: Tentative de connexion échouée pour email={data.email} depuis appareil={data.appareil}")
            raise HTTPException(status_code=401, detail="Email ou mot de passe incorrect")

        access_token, jti = create_access_token(str(user.id), user.type_utilisateur)
        session_id = str(uuid.uuid4())
        refresh_token = create_refresh_token(str(user.id), session_id)

        expires_at = datetime.now(timezone.utc) + timedelta(
            minutes=settings.access_token_expire_minutes
        )

        session = Session(
            id=uuid.UUID(session_id),
            utilisateur_id=user.id,
            jti=jti,
            est_actif=True,
            date_expiration=expires_at,
            appareil=data.appareil,
            localisation=data.localisation,
        )
        self.db.add(session)

        # Update last login & clear premiere_connexion flag after first real use
        user.derniere_connexion = datetime.now(timezone.utc)
        await self.db.commit()

        return {
            # FIX: id manquant — critique pour identifier l'utilisateur côté Flutter
            "id": str(user.id),
            "token": access_token,
            "refreshToken": refresh_token,
            "sessionId": session_id,
            "email": user.email,
            "nom": user.nom,
            "prenom": user.prenom,
            "typeUtilisateur": user.type_utilisateur,
            "expiresIn": settings.access_token_expire_minutes * 60,
            "doitChangerMotDePasse": user.premiere_connexion,
        }

    # ── Logout ────────────────────────────────────────────────────────────────

    async def logout(self, jti: str) -> None:
        await self.db.execute(
            update(Session).where(Session.jti == jti).values(est_actif=False)
        )
        await self.db.commit()

    # ── Refresh token ──────────────────────────────────────────────────────────

    async def refresh(self, data: RefreshRequest) -> dict:
        from fastapi import HTTPException
        from jose import JWTError

        try:
            payload = verify_token(data.refreshToken)
        except JWTError:
            raise HTTPException(status_code=401, detail="Refresh token invalide ou expiré")

        if payload.get("type") != "refresh":
            raise HTTPException(status_code=401, detail="Token invalide")
        if payload.get("session_id") != data.sessionId:
            raise HTTPException(status_code=401, detail="Session non correspondante")

        user_id = payload.get("sub")
        user = (
            await self.db.execute(
                select(Utilisateur).where(
                    Utilisateur.id == uuid.UUID(user_id),
                    Utilisateur.est_actif == True,
                )
            )
        ).scalar_one_or_none()

        if not user:
            raise HTTPException(status_code=401, detail="Utilisateur introuvable")

        # Revoke old session
        await self.db.execute(
            update(Session)
            .where(Session.id == uuid.UUID(data.sessionId))
            .values(est_actif=False)
        )

        # Create new session
        new_access_token, new_jti = create_access_token(str(user.id), user.type_utilisateur)
        new_session_id = str(uuid.uuid4())
        new_refresh_token = create_refresh_token(str(user.id), new_session_id)
        expires_at = datetime.now(timezone.utc) + timedelta(
            minutes=settings.access_token_expire_minutes
        )

        new_session = Session(
            id=uuid.UUID(new_session_id),
            utilisateur_id=user.id,
            jti=new_jti,
            est_actif=True,
            date_expiration=expires_at,
        )
        self.db.add(new_session)
        await self.db.commit()

        return {
            "token": new_access_token,
            "refreshToken": new_refresh_token,
            "sessionId": new_session_id,
            "expiresIn": settings.access_token_expire_minutes * 60,
        }

    # ── Validate token ─────────────────────────────────────────────────────────

    async def validate_and_get_user(self, user: Utilisateur) -> dict:
        """
        FIX: Retourne id + nom + prenom pour permettre la reconstruction
        complète du User Flutter au démarrage à froid.
        """
        return {
            "valid": True,
            "id": str(user.id),
            "email": user.email,
            "nom": user.nom,
            "prenom": user.prenom,
            "typeUtilisateur": user.type_utilisateur,
            "telephone": user.telephone,
            "filiere": user.filiere,
            "campus": user.campus,
            "avatarUrl": user.avatar_url,
        }

    # ── Forgot password ────────────────────────────────────────────────────────

    async def forgot_password(self, data: ForgotPasswordRequest) -> None:
        """
        FIX: Génère un token de réinitialisation et le stocke en base.
        En production, envoyer un email avec le lien contenant le token.
        On répond toujours 200 pour ne pas révéler si l'email existe.
        """
        user = (
            await self.db.execute(
                select(Utilisateur).where(
                    Utilisateur.email == data.email,
                    Utilisateur.est_actif == True,
                )
            )
        ).scalar_one_or_none()

        if user is None:
            # Sécurité : on ne révèle pas si l'email existe
            return

        # Génère un token sécurisé valable 1 heure
        reset_token = secrets.token_urlsafe(32)
        expires_at = datetime.now(timezone.utc) + timedelta(hours=1)

        # Stocke le token haché dans la session (champ réservé au reset)
        # On utilise une session inactive dédiée comme vecteur de stockage
        reset_session = Session(
            id=uuid.uuid4(),
            utilisateur_id=user.id,
            jti=f"reset:{reset_token}",
            est_actif=False,  # Non active → pas un vrai JWT
            date_expiration=expires_at,
            appareil="password_reset",
        )
        self.db.add(reset_session)
        await self.db.commit()

        if settings.debug:
            logger.warning(
                "RESET_TOKEN pour %s: %s (expire le %s)",
                user.email,
                reset_token,
                expires_at.isoformat(),
            )

        # TODO: Envoyer email avec lien contenant reset_token
        # Exemple : f"{settings.frontend_url}/reset-password?token={reset_token}"
        # email_service.send_reset_email(user.email, reset_token)

    # ── Reset password ─────────────────────────────────────────────────────────

    async def reset_password(self, data: ResetPasswordRequest) -> None:
        """
        FIX: Valide le token de réinitialisation et met à jour le mot de passe.
        """
        from fastapi import HTTPException

        # Cherche la session de reset associée à ce token
        jti_key = f"reset:{data.token}"
        reset_session = (
            await self.db.execute(
                select(Session).where(
                    Session.jti == jti_key,
                    Session.appareil == "password_reset",
                )
            )
        ).scalar_one_or_none()

        if reset_session is None:
            raise HTTPException(status_code=400, detail="Token de réinitialisation invalide")

        if reset_session.date_expiration < datetime.now(timezone.utc):
            raise HTTPException(status_code=400, detail="Token de réinitialisation expiré")

        # Récupère l'utilisateur
        user = (
            await self.db.execute(
                select(Utilisateur).where(
                    Utilisateur.id == reset_session.utilisateur_id,
                    Utilisateur.est_actif == True,
                )
            )
        ).scalar_one_or_none()

        if not user:
            raise HTTPException(status_code=404, detail="Utilisateur introuvable")

        # Met à jour le mot de passe
        user.mot_de_passe_hash = await hash_password_async(data.nouveauMotDePasse)
        user.premiere_connexion = False

        # Invalide le token de reset (consommé une seule fois)
        await self.db.delete(reset_session)

        # Invalide toutes les sessions actives (sécurité : déconnexion partout)
        await self.db.execute(
            update(Session)
            .where(
                Session.utilisateur_id == user.id,
                Session.est_actif == True,
            )
            .values(est_actif=False)
        )

        await self.db.commit()
