use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
    Json,
};
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use serde::{Deserialize, Serialize};
use std::future::Future;
use uuid::Uuid;

// ── JWT Claims ────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
    pub sub: String,       // user_id (UUID string)
    pub email: String,
    pub roles: Vec<String>,
    pub active_role: String,
    pub exp: usize,
    pub iat: usize,
}

// ── AuthUser extractor ────────────────────────────────────────────────────────

/// Axum extractor: validates the Bearer token in the Authorization header.
/// Usage:  `async fn handler(auth: AuthUser, ...) -> impl IntoResponse`
#[derive(Debug, Clone)]
pub struct AuthUser {
    pub user_id: Uuid,
    pub email: String,
    pub claims: Claims,
}

impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    fn from_request_parts(
        parts: &mut Parts,
        _state: &S,
    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
        let auth_header = parts
            .headers
            .get("Authorization")
            .and_then(|v| v.to_str().ok())
            .map(str::to_string);

        async move {
            let auth_header = auth_header.ok_or(AuthError::MissingToken)?;

            // 2. Strip "Bearer " prefix
            let token = auth_header
            .strip_prefix("Bearer ")
            .ok_or(AuthError::InvalidToken)?;

            // 3. Decode & verify
            let jwt_secret = std::env::var("JWT_SECRET")
            .expect("JWT_SECRET must be set — refusing to start with insecure default");

            let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(jwt_secret.as_bytes()),
            &Validation::new(Algorithm::HS256),
            )
        .map_err(|e| {
            tracing::debug!("JWT decode error: {}", e);
            AuthError::InvalidToken
        })?;

            // 4. Parse user_id as UUID
            let user_id = Uuid::parse_str(&token_data.claims.sub)
            .map_err(|_| AuthError::InvalidToken)?;

            Ok(AuthUser {
                user_id,
                email: token_data.claims.email.clone(),
                claims: token_data.claims,
            })
        }
    }
}

// ── Auth Error types ──────────────────────────────────────────────────────────

#[derive(Debug)]
pub enum AuthError {
    MissingToken,
    InvalidToken,
    InsufficientPermissions,
}

impl IntoResponse for AuthError {
    fn into_response(self) -> Response {
        let (status, code, message) = match self {
            AuthError::MissingToken => (
                StatusCode::UNAUTHORIZED,
                "MISSING_TOKEN",
                "Authorization header required",
            ),
            AuthError::InvalidToken => (
                StatusCode::UNAUTHORIZED,
                "INVALID_TOKEN",
                "Token is invalid or expired",
            ),
            AuthError::InsufficientPermissions => (
                StatusCode::FORBIDDEN,
                "INSUFFICIENT_PERMISSIONS",
                "You do not have permission to access this resource",
            ),
        };

        (
            status,
            Json(serde_json::json!({ "error": message, "code": code })),
        )
            .into_response()
    }
}

// ── Role guard helper ─────────────────────────────────────────────────────────

/// Returns Ok if the user's active_role matches the expected role key.
/// Otherwise returns AuthError::InsufficientPermissions.
pub fn require_role(auth: &AuthUser, expected_role: &str) -> Result<(), AuthError> {
    if auth.claims.active_role == expected_role
        || auth.claims.roles.contains(&expected_role.to_string())
    {
        Ok(())
    } else {
        Err(AuthError::InsufficientPermissions)
    }
}

/// Returns Ok if the user has an internal admin role (ADMIN or SUPER_ADMIN).
pub fn require_admin(auth: &AuthUser) -> Result<(), AuthError> {
    let active = auth.claims.active_role.as_str();
    let has_internal_admin =
        active == "ADMIN"
            || active == "SUPER_ADMIN"
            || auth.claims.roles.contains(&"ADMIN".to_string())
            || auth.claims.roles.contains(&"SUPER_ADMIN".to_string());

    if has_internal_admin {
        Ok(())
    } else {
        Err(AuthError::InsufficientPermissions)
    }
}
