//! Authentication Flow Integration Tests
//!
//! Comprehensive tests for the 8-layer authentication pipeline:
//! 1. mTLS client certificate validation
//! 2. JWT extraction from Authorization header
//! 3. JWT revocation check (Redis)
//! 4. JWT signature and expiration validation
//! 5. RBAC permission check
//! 6. Rate limiting
//! 7. User context injection
//! 8. Async audit logging

#[path = "common/mod.rs"]
mod common;

use anyhow::Result;
use common::{
    cleanup_redis, generate_expired_token, generate_invalid_signature_token, generate_test_token,
    wait_for_redis, TestJwtConfig,
};
use std::time::{Duration, Instant};
use tonic::{metadata::MetadataValue, Request};

use api::auth::{
    AuditLogger, AuthInterceptor, AuthzService, Jti, JwtService, RateLimiter, RevocationService,
};

const REDIS_URL: &str = "redis://localhost:6379";

/// Setup test authentication components
async fn setup_auth_components() -> Result<AuthInterceptor> {
    wait_for_redis(REDIS_URL, 50).await?;
    cleanup_redis(REDIS_URL).await?;

    let config = TestJwtConfig::default();

    let jwt_service = JwtService::new(config.secret, config.issuer, config.audience);

    let revocation_service = RevocationService::new(REDIS_URL).await?;
    let authz_service = AuthzService::new();
    let rate_limiter = RateLimiter::new(100).map_err(|e| anyhow::anyhow!(e))?; // 100 req/s
    let audit_logger = AuditLogger::new(true);

    Ok(AuthInterceptor::new(
        jwt_service,
        revocation_service,
        authz_service,
        rate_limiter,
        audit_logger,
    ))
}

#[tokio::test]
async fn test_successful_authentication() -> Result<()> {
    println!("\n=== Test: Successful Authentication ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate valid token
    let (token, _jti) = generate_test_token(
        "user123",
        vec!["trader".to_string()],
        vec!["api.access".to_string(), "trading.submit".to_string()],
        3600,
    )?;

    // Create request with Authorization header
    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    // Measure authentication time
    let start = Instant::now();
    let result = auth_interceptor.clone().authenticate(request).await;
    let elapsed = start.elapsed();

    println!("✓ Authentication succeeded in {:?}", elapsed);
    println!("  Performance target: <10μs, Actual: {:?}", elapsed);

    assert!(result.is_ok(), "Authentication should succeed");

    // Verify user context was injected
    let authenticated_request = result.unwrap();
    let extensions = authenticated_request.extensions();

    assert!(
        extensions.get::<api::auth::UserContext>().is_some(),
        "User context should be injected"
    );

    if let Some(user_ctx) = extensions.get::<api::auth::UserContext>() {
        assert_eq!(user_ctx.user_id, "user123");
        assert!(user_ctx.roles.contains(&"trader".to_string()));
        assert!(user_ctx.permissions.contains(&"api.access".to_string()));
        println!("✓ User context verified: user_id={}", user_ctx.user_id);
    }

    // Warn if latency exceeds target
    if elapsed > Duration::from_micros(10) {
        println!(
            "⚠ WARNING: Authentication latency {:?} exceeds 10μs target",
            elapsed
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_missing_jwt_rejected() -> Result<()> {
    println!("\n=== Test: Missing JWT Rejected ===");

    let auth_interceptor = setup_auth_components().await?;

    // Create request without Authorization header
    let request = Request::new(());

    let result = auth_interceptor.clone().authenticate(request).await;

    assert!(result.is_err(), "Request without JWT should be rejected");

    if let Err(status) = result {
        assert_eq!(status.code(), tonic::Code::Unauthenticated);
        println!("✓ Request rejected with status: {}", status.code());
        println!("  Message: {}", status.message());
    }

    Ok(())
}

#[tokio::test]
async fn test_revoked_jwt_rejected() -> Result<()> {
    println!("\n=== Test: Revoked JWT Rejected ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate valid token
    let (token, jti) = generate_test_token(
        "user456",
        vec!["trader".to_string()],
        vec!["api.access".to_string()],
        3600,
    )?;

    // Add token to blacklist
    let revocation_service = RevocationService::new(REDIS_URL).await?;
    revocation_service
        .revoke_token(&Jti::from_string(jti), 3600)
        .await?;

    println!("✓ Token added to blacklist");

    // Create request with revoked token
    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    let result = auth_interceptor.clone().authenticate(request).await;

    assert!(result.is_err(), "Revoked token should be rejected");

    if let Err(status) = result {
        assert_eq!(status.code(), tonic::Code::Unauthenticated);
        println!("✓ Revoked token rejected with status: {}", status.code());
        assert!(
            status.message().contains("revoked"),
            "Error message should mention revocation"
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_expired_jwt_rejected() -> Result<()> {
    println!("\n=== Test: Expired JWT Rejected ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate expired token
    let token = generate_expired_token("user789")?;

    // Create request with expired token
    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    let result = auth_interceptor.clone().authenticate(request).await;

    assert!(result.is_err(), "Expired token should be rejected");

    if let Err(status) = result {
        assert_eq!(status.code(), tonic::Code::Unauthenticated);
        println!("✓ Expired token rejected with status: {}", status.code());
    }

    Ok(())
}

#[tokio::test]
async fn test_invalid_signature_rejected() -> Result<()> {
    println!("\n=== Test: Invalid Signature Rejected ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate token with wrong signature
    let token = generate_invalid_signature_token("attacker")?;

    // Create request with invalid token
    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    let result = auth_interceptor.clone().authenticate(request).await;

    assert!(
        result.is_err(),
        "Token with invalid signature should be rejected"
    );

    if let Err(status) = result {
        assert_eq!(status.code(), tonic::Code::Unauthenticated);
        println!(
            "✓ Invalid signature rejected with status: {}",
            status.code()
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_rbac_permission_denied() -> Result<()> {
    println!("\n=== Test: RBAC Permission Denied ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate token without api.access permission
    let (token, _jti) = generate_test_token(
        "restricted_user",
        vec!["guest".to_string()],
        vec!["limited.access".to_string()], // Missing api.access
        3600,
    )?;

    // Create request with limited permissions
    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    let result = auth_interceptor.clone().authenticate(request).await;

    assert!(
        result.is_err(),
        "Request without api.access should be denied"
    );

    if let Err(status) = result {
        assert_eq!(status.code(), tonic::Code::PermissionDenied);
        println!("✓ Permission denied with status: {}", status.code());
        println!("  Message: {}", status.message());
    }

    Ok(())
}

#[tokio::test]
async fn test_rate_limit_exceeded() -> Result<()> {
    println!("\n=== Test: Rate Limit Exceeded ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate valid token
    let (token, _jti) = generate_test_token(
        "rate_limited_user",
        vec!["trader".to_string()],
        vec!["api.access".to_string()],
        3600,
    )?;

    let mut success_count = 0;
    let mut rate_limited_count = 0;

    // Make 110 rapid requests (limit is 100/s)
    for i in 1..=110 {
        let mut request = Request::new(());
        request.metadata_mut().insert(
            "authorization",
            MetadataValue::try_from(format!("Bearer {}", token))?,
        );

        let result = auth_interceptor.clone().authenticate(request).await;

        if result.is_ok() {
            success_count += 1;
        } else if let Err(status) = result {
            if status.code() == tonic::Code::ResourceExhausted {
                rate_limited_count += 1;
                if rate_limited_count == 1 {
                    println!("✓ First rate limit hit at request #{}", i);
                }
            }
        }
    }

    println!("  Successful requests: {}", success_count);
    println!("  Rate limited requests: {}", rate_limited_count);

    assert!(
        rate_limited_count > 0,
        "Some requests should be rate limited"
    );
    // Allow small tolerance (3-5 extra) due to token bucket timing granularity
    assert!(
        success_count <= 105,
        "Success count should not significantly exceed limit (got {}, limit 100, tolerance 105)",
        success_count
    );

    Ok(())
}

#[tokio::test]
async fn test_8_layer_auth_performance() -> Result<()> {
    println!("\n=== Test: 8-Layer Authentication Performance ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate valid token
    let (token, _jti) = generate_test_token(
        "perf_user",
        vec!["trader".to_string()],
        vec!["api.access".to_string()],
        3600,
    )?;

    let mut latencies = Vec::new();

    // Perform 100 authentication requests
    println!("  Running 100 authentication requests...");
    for _ in 0..100 {
        let mut request = Request::new(());
        request.metadata_mut().insert(
            "authorization",
            MetadataValue::try_from(format!("Bearer {}", token))?,
        );

        let start = Instant::now();
        let result = auth_interceptor.clone().authenticate(request).await;
        let elapsed = start.elapsed();

        assert!(result.is_ok(), "Authentication should succeed");
        latencies.push(elapsed);
    }

    // Calculate percentiles
    latencies.sort();
    let p50 = latencies[49];
    let p95 = latencies[94];
    let p99 = latencies[98];
    let p999 = latencies[99];

    println!("\n  Performance Metrics:");
    println!("  ├─ P50:   {:?}", p50);
    println!("  ├─ P95:   {:?}", p95);
    println!("  ├─ P99:   {:?}", p99);
    println!("  └─ P99.9: {:?}", p999);

    println!("\n  Target: <10μs per request");

    // Performance assertions (may fail in CI/CD, so we just warn)
    if p99 > Duration::from_micros(10) {
        println!("  ⚠ WARNING: P99 latency {:?} exceeds 10μs target", p99);
    } else {
        println!("  ✓ P99 latency within 10μs target");
    }

    Ok(())
}

#[tokio::test]
async fn test_concurrent_authentication() -> Result<()> {
    println!("\n=== Test: Concurrent Authentication ===");

    let auth_interceptor = setup_auth_components().await?;

    // Generate tokens for 10 different users
    let mut tokens = Vec::new();
    for i in 1..=10 {
        let (token, _jti) = generate_test_token(
            &format!("concurrent_user_{}", i),
            vec!["trader".to_string()],
            vec!["api.access".to_string()],
            3600,
        )?;
        tokens.push(token);
    }

    // Spawn 100 concurrent authentication requests
    let mut handles = Vec::new();

    println!("  Spawning 100 concurrent authentication requests...");
    for i in 0..100 {
        let token = tokens[i % 10].clone();
        let auth = auth_interceptor.clone();

        let handle = tokio::spawn(async move {
            let mut request = Request::new(());
            request.metadata_mut().insert(
                "authorization",
                MetadataValue::try_from(format!("Bearer {}", token)).unwrap(),
            );

            auth.authenticate(request).await
        });

        handles.push(handle);
    }

    // Wait for all requests to complete
    let mut success_count = 0;
    for handle in handles {
        if let Ok(result) = handle.await {
            if result.is_ok() {
                success_count += 1;
            }
        }
    }

    println!(
        "  ✓ {}/100 concurrent authentications succeeded",
        success_count
    );
    assert_eq!(success_count, 100, "All concurrent requests should succeed");

    Ok(())
}

#[tokio::test]
async fn test_user_context_injection() -> Result<()> {
    println!("\n=== Test: User Context Injection (Layer 7) ===");

    let auth_interceptor = setup_auth_components().await?;

    let (token, _jti) = generate_test_token(
        "context_user",
        vec!["admin".to_string(), "trader".to_string()],
        vec!["api.access".to_string(), "admin.manage".to_string()],
        3600,
    )?;

    let mut request = Request::new(());
    request.metadata_mut().insert(
        "authorization",
        MetadataValue::try_from(format!("Bearer {}", token))?,
    );

    let result = auth_interceptor.clone().authenticate(request).await?;

    // Verify user context
    let user_ctx = result
        .extensions()
        .get::<api::auth::UserContext>()
        .expect("UserContext should be present");

    println!("  ✓ User context injected:");
    println!("    ├─ User ID: {}", user_ctx.user_id);
    println!("    ├─ Roles: {:?}", user_ctx.roles);
    println!("    ├─ Permissions: {:?}", user_ctx.permissions);
    println!("    └─ Session ID: {}", user_ctx.session_id);

    assert_eq!(user_ctx.user_id, "context_user");
    assert_eq!(user_ctx.roles.len(), 2);
    assert_eq!(user_ctx.permissions.len(), 2);
    assert!(user_ctx.roles.contains(&"admin".to_string()));
    assert!(user_ctx.permissions.contains(&"admin.manage".to_string()));

    Ok(())
}

#[tokio::test]
async fn test_malformed_authorization_header() -> Result<()> {
    println!("\n=== Test: Malformed Authorization Header ===");

    let auth_interceptor = setup_auth_components().await?;

    let test_cases = vec![
        ("Basic dXNlcjpwYXNzd29yZA==", "Basic auth instead of Bearer"),
        ("Bearer", "Bearer without token"),
        ("Bearer ", "Bearer with empty token"),
        ("", "Empty header"),
        ("InvalidFormat token123", "Invalid format"),
    ];

    for (header_value, description) in test_cases {
        let mut request = Request::new(());
        if !header_value.is_empty() {
            request
                .metadata_mut()
                .insert("authorization", MetadataValue::try_from(header_value)?);
        }

        let result = auth_interceptor.clone().authenticate(request).await;
        assert!(result.is_err(), "{} should be rejected", description);
        println!("  ✓ Rejected: {}", description);
    }

    Ok(())
}
