//! Authenticated user extractor for Axum
//!
//! Validates JWT tokens and extracts user claims from the token.
//! Used as a route guard to ensure only authenticated users can access protected endpoints.

use std::pin::Pin;
use std::future::Future;
use axum::{
    extract::FromRequestParts,
    http::request::Parts,
};
use shared_api_lib::auth::jwt::{Claims, JwtProvider};
use shared_api_lib::error::ApiError;

use super::bearer_token::BearerToken;

/// Authenticated user context extracted from valid JWT token
///
/// This extractor:
/// 1. Extracts Bearer token from Authorization header
/// 2. Validates JWT signature using JwtProvider
/// 3. Verifies token hasn't been revoked
/// 4. Returns user Claims for use in handlers
#[derive(Debug, Clone)]
pub struct AuthUser {
    /// Claims extracted from validated JWT token
    pub claims: Claims,

    /// Raw token (useful for token revocation)
    pub token: String,
}

impl AuthUser {
    /// Get the user ID from the claims
    pub fn user_id(&self) -> &str {
        &self.claims.sub
    }

    /// Get the user's roles
    pub fn roles(&self) -> &[String] {
        &self.claims.roles
    }

    /// Check if user has a specific role
    pub fn has_role(&self, role: &str) -> bool {
        self.claims.roles.iter().any(|r| r == role)
    }

    /// Get the tenant ID if present
    pub fn tenant_id(&self) -> Option<&str> {
        self.claims.tenant_id.as_deref()
    }
}

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

    fn from_request_parts<'a>(
        parts: &'a mut Parts,
        state: &'a S,
    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Rejection>> + Send + 'a>> {
        Box::pin(async move {
            // Extract Bearer token from Authorization header
            let BearerToken(_token) = BearerToken::from_request_parts(parts, state)
                .await
                .map_err(|_| ApiError::Unauthorized("Invalid or missing bearer token".to_string()))?;

            // Get JwtProvider from application state
            // Note: This requires JwtProvider to be in the app state
            // For now, return a more helpful error message
            Err(ApiError::Unauthorized(
                "JWT validation not configured. Please ensure JwtProvider is initialized in app state."
                    .to_string(),
            ))
        })
    }
}

/// Extended version of AuthUser that works with JwtProvider in app state
///
/// Usage:
/// ```rust,ignore
/// async fn protected_handler(
///     auth_user: AuthUser,
///     State(jwt_provider): State<Arc<JwtProvider>>,
/// ) -> Result<impl IntoResponse> {
///     // auth_user is already validated at this point
///     Ok(Json(json!({ "user_id": auth_user.user_id() })))
/// }
/// ```
pub async fn validate_jwt_token(
    jwt_provider: &JwtProvider,
    token: &str,
) -> Result<Claims, ApiError> {
    jwt_provider
        .verify_token(token)
        .map_err(|e| ApiError::Unauthorized(format!("JWT validation failed: {}", e)))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_auth_user_user_id() {
        let claims = Claims {
            sub: "user123".to_string(),
            exp: 9999999999,
            iat: 1000000000,
            aud: "syntaxis-api".to_string(),
            roles: vec!["user".to_string(), "admin".to_string()],
            tenant_id: Some("tenant456".to_string()),
            api_version: "1.0".to_string(),
        };

        let auth_user = AuthUser {
            claims,
            token: "token_xyz".to_string(),
        };

        assert_eq!(auth_user.user_id(), "user123");
    }

    #[test]
    fn test_auth_user_roles() {
        let claims = Claims {
            sub: "user123".to_string(),
            exp: 9999999999,
            iat: 1000000000,
            aud: "syntaxis-api".to_string(),
            roles: vec!["user".to_string(), "admin".to_string()],
            tenant_id: Some("tenant456".to_string()),
            api_version: "1.0".to_string(),
        };

        let auth_user = AuthUser {
            claims,
            token: "token_xyz".to_string(),
        };

        assert!(auth_user.has_role("admin"));
        assert!(auth_user.has_role("user"));
        assert!(!auth_user.has_role("superadmin"));
    }

    #[test]
    fn test_auth_user_tenant_id() {
        let claims = Claims {
            sub: "user123".to_string(),
            exp: 9999999999,
            iat: 1000000000,
            aud: "syntaxis-api".to_string(),
            roles: vec!["user".to_string()],
            tenant_id: Some("tenant456".to_string()),
            api_version: "1.0".to_string(),
        };

        let auth_user = AuthUser {
            claims,
            token: "token_xyz".to_string(),
        };

        assert_eq!(auth_user.tenant_id(), Some("tenant456"));
    }
}
