//! gRPC authentication interceptor
//!
//! Adds JWT Bearer tokens to all outgoing gRPC requests using tonic interceptors.

use tonic::{metadata::MetadataValue, service::Interceptor, Request, Status};

use super::token_manager::{AuthTokenManager, TokenStorage};

/// gRPC authentication interceptor
///
/// Automatically adds "Authorization: Bearer `<token>`" header to all outgoing
/// gRPC requests. If no valid token is available, requests proceed without
/// authentication (allowing login requests to work).
#[derive(Clone)]
pub struct AuthInterceptor<S: TokenStorage> {
    /// Token manager for retrieving current access token
    auth_manager: AuthTokenManager<S>,
}

impl<S: TokenStorage> AuthInterceptor<S> {
    /// Create a new authentication interceptor
    pub const fn new(auth_manager: AuthTokenManager<S>) -> Self {
        Self { auth_manager }
    }
}

impl<S: TokenStorage + 'static> Interceptor for AuthInterceptor<S> {
    fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
        // Get cached access token synchronously (safe for gRPC interceptor)
        let token = self.auth_manager.get_cached_access_token();

        if let Some(access_token) = token {
            // Format as "Bearer <token>"
            let bearer_token = format!("Bearer {}", access_token);

            // Parse as metadata value
            let token_value = bearer_token.parse::<MetadataValue<_>>().map_err(|e| {
                tracing::error!("JWT metadata value parse rejected: {}", e);
                Status::internal("Invalid token format")
            })?;

            // Add to request metadata
            request.metadata_mut().insert("authorization", token_value);

            tracing::trace!("Added JWT Bearer token to gRPC request");
        } else {
            tracing::trace!("No valid access token - request proceeding without authentication");
        }

        Ok(request)
    }
}

#[cfg(test)]
#[allow(clippy::shadow_unrelated, clippy::shadow_reuse, clippy::unwrap_used, clippy::expect_used, clippy::assertions_on_result_states, clippy::str_to_string)]
mod tests {
    use super::*;
    use crate::auth::token_manager::{InMemoryTokenStorage, TokenInfo};
    use std::time::{SystemTime, UNIX_EPOCH};

    #[tokio::test(flavor = "multi_thread")]
    async fn test_interceptor_adds_token() {
        let storage = InMemoryTokenStorage::new();
        let manager = AuthTokenManager::new(storage);

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        let token_info = TokenInfo {
            access_token: "test_access_token".to_owned(),
            refresh_token: "test_refresh_token".to_owned(),
            expires_at: now + 3600,
        };

        manager.set_tokens(token_info).await.unwrap();

        let mut interceptor = AuthInterceptor::new(manager);
        let request = Request::new(());

        let result = interceptor.call(request);
        assert!(result.is_ok());

        let request = result.unwrap();
        let auth_header = request.metadata().get("authorization");
        assert!(auth_header.is_some());

        let auth_value = auth_header.unwrap().to_str().unwrap();
        assert_eq!(auth_value, "Bearer test_access_token");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_interceptor_without_token() {
        let storage = InMemoryTokenStorage::new();
        let manager = AuthTokenManager::new(storage);

        let mut interceptor = AuthInterceptor::new(manager);
        let request = Request::new(());

        let result = interceptor.call(request);
        assert!(result.is_ok());

        let request = result.unwrap();
        let auth_header = request.metadata().get("authorization");
        assert!(auth_header.is_none());
    }
}
