use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use uuid::Uuid;

use crate::auth::jwt;
use crate::error::AppError;
use crate::models::session::Session;
use crate::models::user::UserRole;
use crate::state::AppState;

#[derive(Debug, Clone)]
pub struct AuthUser {
    pub user_id: Uuid,
    pub event_id: Uuid,
    pub role: UserRole,
    /// Live ban flag. Banned users keep *read* access (per USER_JOURNEYS §10), so
    /// the base extractor does NOT reject them — write handlers and the
    /// Require{Host,Admin} extractors enforce the ban instead.
    pub is_banned: bool,
    pub token_hash: String,
}

impl FromRequestParts<AppState> for AuthUser {
    type Rejection = AppError;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let header = parts
            .headers
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .ok_or_else(|| AppError::Unauthorized("Token fehlt.".into()))?;

        let token = header
            .strip_prefix("Bearer ")
            .ok_or_else(|| AppError::Unauthorized("Ungültiges Token-Format.".into()))?;

        // Verify the JWT's signature. Expiry is deliberately NOT enforced here (see
        // `jwt::verify_token`) — the authoritative, sliding session lifetime lives in the
        // `session` row read below. We also don't trust the token's role/ban claims; the
        // live user row is authoritative, so the decoded claims aren't needed beyond this.
        jwt::verify_token(token, &state.config.jwt_secret)
            .map_err(|_| AppError::Unauthorized("Token ungültig oder abgelaufen.".into()))?;

        let token_hash = jwt::hash_token(token);

        // Single round-trip: resolve the session token to its *live* user row. A
        // role/ban stored in the token would survive a demote/ban for the full session
        // lifetime (up to 30d), so we always re-read the user (a demoted host loses host
        // powers immediately). We do NOT reject banned users here — they retain read
        // access by design; writes and host/admin actions enforce the ban downstream.
        let user = Session::find_user_by_token_hash(&state.pool, &token_hash)
            .await
            .map_err(|e| AppError::Internal(e.into()))?
            .ok_or_else(|| {
                AppError::Unauthorized("Sitzung nicht gefunden oder abgelaufen.".into())
            })?;

        // Touch last_seen_at AND slide the session's expiry forward (fire-and-forget), so
        // an active client's session renews instead of hitting the fixed 30-day cliff.
        // Admin sessions keep their tighter 1-day window (they renew on activity but still
        // lapse a day after the admin goes idle). Failures are non-fatal but worth
        // surfacing — silent swallowing hides DB connection pressure that would otherwise
        // be the first symptom of a real problem.
        let pool = state.pool.clone();
        let touch_hash = token_hash.clone();
        let expiry_days = if user.role == UserRole::Admin {
            1
        } else {
            state.config.session_expiry_days
        };
        tokio::spawn(async move {
            if let Err(e) = Session::touch_and_renew(&pool, &touch_hash, expiry_days).await {
                tracing::warn!(error = ?e, "session touch/renew failed");
            }
        });

        Ok(Self {
            user_id: user.id,
            event_id: user.event_id,
            role: user.role,
            is_banned: user.is_banned,
            token_hash,
        })
    }
}

/// Extractor that requires at least Host role.
pub struct RequireHost(pub AuthUser);

impl FromRequestParts<AppState> for RequireHost {
    type Rejection = AppError;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let auth = AuthUser::from_request_parts(parts, state).await?;
        if auth.is_banned {
            return Err(AppError::Forbidden("Du bist gesperrt.".into()));
        }
        match auth.role {
            UserRole::Host | UserRole::Admin => Ok(Self(auth)),
            _ => Err(AppError::Forbidden("Nur für Hosts und Admins.".into())),
        }
    }
}

/// Extractor that requires Admin role.
pub struct RequireAdmin(pub AuthUser);

impl FromRequestParts<AppState> for RequireAdmin {
    type Rejection = AppError;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let auth = AuthUser::from_request_parts(parts, state).await?;
        if auth.is_banned {
            return Err(AppError::Forbidden("Du bist gesperrt.".into()));
        }
        match auth.role {
            UserRole::Admin => Ok(Self(auth)),
            _ => Err(AppError::Forbidden("Nur für Admins.".into())),
        }
    }
}
