#![allow(
    clippy::manual_range_contains,
    clippy::int_plus_one,
    dead_code,
    unused_variables
)]
//! Advanced Rate Limiter Tests - Wave 17 Agent 17.10
//!
//! Comprehensive tests for token bucket algorithm, cache management,
//! endpoint configuration, and error handling with Redis backend.
//!
//! Coverage targets:
//! - Token bucket refill mechanics
//! - LRU cache eviction
//! - Endpoint configuration updates
//! - Redis connection handling
//! - Concurrent access patterns

use anyhow::Result;
use std::time::Duration;
use uuid::Uuid;

use api::routing::{RateLimitConfig, RateLimiter};

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

// ============================================================================
// Token Bucket Mechanics Tests (5 tests)
// ============================================================================

#[tokio::test]
async fn test_token_bucket_capacity_enforcement() -> Result<()> {
    println!("\n=== Test: Token Bucket Capacity Enforcement ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // config.update has 10 req/s capacity
    let mut allowed = 0;
    for _ in 0..20 {
        if rate_limiter.check_limit(&user_id, "config.update").await? {
            allowed += 1;
        } else {
            break;
        }
    }

    println!("  Allowed: {}/20", allowed);
    assert!(
        allowed >= 10 && allowed <= 12,
        "Should allow ~10 requests (capacity)"
    );

    Ok(())
}

#[tokio::test]
async fn test_token_bucket_refill_rate() -> Result<()> {
    println!("\n=== Test: Token Bucket Refill Rate ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Exhaust bucket
    for _ in 0..15 {
        let _ = rate_limiter.check_limit(&user_id, "config.update").await?;
    }

    // Wait for partial refill (0.5s = ~5 tokens at 10 req/s)
    tokio::time::sleep(Duration::from_millis(500)).await;

    let mut refilled = 0;
    for _ in 0..10 {
        if rate_limiter.check_limit(&user_id, "config.update").await? {
            refilled += 1;
        } else {
            break;
        }
    }

    println!("  Refilled: {} tokens in 500ms", refilled);
    assert!(refilled >= 4 && refilled <= 6, "Should refill ~5 tokens");

    Ok(())
}

#[tokio::test]
async fn test_token_bucket_burst_handling() -> Result<()> {
    println!("\n=== Test: Token Bucket Burst Handling ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // trading.submit_order has 100 req/s capacity
    let mut burst_allowed = 0;

    // Make burst of 150 requests
    for _ in 0..150 {
        if rate_limiter
            .check_limit(&user_id, "trading.submit_order")
            .await?
        {
            burst_allowed += 1;
        } else {
            break;
        }
    }

    println!("  Burst allowed: {}/150", burst_allowed);
    assert!(
        burst_allowed >= 95 && burst_allowed <= 105,
        "Should handle burst up to capacity"
    );

    Ok(())
}

#[tokio::test]
async fn test_token_bucket_multiple_endpoints() -> Result<()> {
    println!("\n=== Test: Multiple Endpoints Independent Limits ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Exhaust one endpoint
    for _ in 0..20 {
        let _ = rate_limiter.check_limit(&user_id, "config.update").await?;
    }

    // Other endpoint should have full capacity
    let mut trading_allowed = 0;
    for _ in 0..50 {
        if rate_limiter
            .check_limit(&user_id, "trading.submit_order")
            .await?
        {
            trading_allowed += 1;
        } else {
            break;
        }
    }

    println!("  Trading endpoint allowed: {}", trading_allowed);
    assert!(trading_allowed >= 45, "Should have independent limit");

    Ok(())
}

#[tokio::test]
async fn test_token_bucket_slow_refill() -> Result<()> {
    println!("\n=== Test: Slow Refill Rate (Backtesting) ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // backtesting.run has 5 req/min (very slow refill)
    let mut initial = 0;
    for _ in 0..10 {
        if rate_limiter
            .check_limit(&user_id, "backtesting.run")
            .await?
        {
            initial += 1;
        } else {
            break;
        }
    }

    println!("  Initial allowed: {}", initial);
    assert!(initial <= 6, "Should be rate limited");

    // Wait 12 seconds (should refill ~1 token)
    tokio::time::sleep(Duration::from_secs(12)).await;

    let mut refilled = 0;
    for _ in 0..5 {
        if rate_limiter
            .check_limit(&user_id, "backtesting.run")
            .await?
        {
            refilled += 1;
        } else {
            break;
        }
    }

    println!("  After 12s: {} new tokens", refilled);
    assert!(refilled >= 0 && refilled <= 2, "Should refill slowly");

    Ok(())
}

// ============================================================================
// Cache Management Tests (5 tests)
// ============================================================================

#[tokio::test]
async fn test_cache_hit_after_first_check() -> Result<()> {
    println!("\n=== Test: Cache Hit After First Check ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // First check (cache miss, hits Redis)
    let start = std::time::Instant::now();
    let _ = rate_limiter
        .check_limit(&user_id, "trading.submit_order")
        .await?;
    let first_duration = start.elapsed();

    // Second check (cache hit, <8ns expected)
    let start = std::time::Instant::now();
    let _ = rate_limiter
        .check_limit(&user_id, "trading.submit_order")
        .await?;
    let second_duration = start.elapsed();

    println!("  First check:  {:?}", first_duration);
    println!("  Second check: {:?}", second_duration);

    // Second check should be much faster
    assert!(second_duration < first_duration);

    Ok(())
}

#[tokio::test]
async fn test_cache_expiration() -> Result<()> {
    println!("\n=== Test: Cache TTL Expiration ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Make initial check
    let _ = rate_limiter
        .check_limit(&user_id, "trading.submit_order")
        .await?;

    // Wait for cache TTL to expire (1 second)
    tokio::time::sleep(Duration::from_millis(1100)).await;

    // Next check should be cache miss (go to Redis)
    let start = std::time::Instant::now();
    let _ = rate_limiter
        .check_limit(&user_id, "trading.submit_order")
        .await?;
    let duration = start.elapsed();

    println!("  After TTL expiry: {:?}", duration);
    // Should hit Redis again
    assert!(duration > Duration::from_micros(100));

    Ok(())
}

#[tokio::test]
async fn test_cache_size_limit_and_eviction() -> Result<()> {
    println!("\n=== Test: Cache LRU Eviction ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;

    // Fill cache with many different users (10,000+ entries)
    println!("  Creating 10,500 cache entries...");
    for i in 0..10_500 {
        let user_id = Uuid::new_v4();
        let _ = rate_limiter
            .check_limit(&user_id, "trading.submit_order")
            .await?;

        if i % 1000 == 0 {
            println!("    Created {} entries", i);
        }
    }

    let stats = rate_limiter.get_cache_stats().await;

    println!("  Cache size: {}/{}", stats.size, stats.max_size);
    println!("  LRU eviction: {} entries removed", 10_500 - stats.size);

    // Cache should not exceed max size
    assert!(
        stats.size <= stats.max_size,
        "Cache should not exceed max size"
    );
    assert!(stats.size >= 9_000, "Cache should keep most recent entries");

    Ok(())
}

#[tokio::test]
async fn test_cache_clear_operation() -> Result<()> {
    println!("\n=== Test: Cache Clear Operation ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;

    // Populate cache
    for _ in 0..100 {
        let user_id = Uuid::new_v4();
        let _ = rate_limiter
            .check_limit(&user_id, "trading.submit_order")
            .await?;
    }

    let stats_before = rate_limiter.get_cache_stats().await;
    println!("  Cache before clear: {} entries", stats_before.size);

    // Clear cache
    rate_limiter.clear_cache().await;

    let stats_after = rate_limiter.get_cache_stats().await;
    println!("  Cache after clear: {} entries", stats_after.size);

    assert_eq!(stats_after.size, 0, "Cache should be empty after clear");

    Ok(())
}

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

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Spawn 100 concurrent tasks accessing same cache entry
    let mut handles = vec![];

    for _ in 0..100 {
        let limiter = rate_limiter.clone();
        let uid = user_id;

        let handle =
            tokio::spawn(async move { limiter.check_limit(&uid, "trading.submit_order").await });

        handles.push(handle);
    }

    // Collect results
    let mut success_count = 0;
    let mut error_count = 0;

    for handle in handles {
        match handle.await {
            Ok(Ok(_)) => success_count += 1,
            Ok(Err(_)) => error_count += 1,
            Err(_) => error_count += 1,
        }
    }

    println!("  Success: {}, Errors: {}", success_count, error_count);
    assert_eq!(
        success_count + error_count,
        100,
        "All tasks should complete"
    );
    assert_eq!(error_count, 0, "No errors should occur");

    Ok(())
}

// ============================================================================
// Endpoint Configuration Tests (5 tests)
// ============================================================================

#[tokio::test]
async fn test_default_endpoint_config() -> Result<()> {
    println!("\n=== Test: Default Endpoint Configuration ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Unknown endpoint should use default config (50 req/s)
    let mut allowed = 0;
    for _ in 0..75 {
        if rate_limiter
            .check_limit(&user_id, "unknown.endpoint")
            .await?
        {
            allowed += 1;
        } else {
            break;
        }
    }

    println!("  Default endpoint allowed: {}/75", allowed);
    assert!(
        allowed >= 45 && allowed <= 55,
        "Should use default limit (50 req/s)"
    );

    Ok(())
}

#[tokio::test]
async fn test_update_endpoint_config() -> Result<()> {
    println!("\n=== Test: Dynamic Endpoint Configuration ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;

    // Create custom config
    let custom_config = RateLimitConfig {
        endpoint: "custom.endpoint".to_string(),
        capacity: 20.0,
        refill_rate: 20.0,
        burst_size: 5,
    };

    // Update configuration
    rate_limiter.set_endpoint_config(custom_config).await;

    let user_id = Uuid::new_v4();

    // Test custom limit
    let mut allowed = 0;
    for _ in 0..30 {
        if rate_limiter
            .check_limit(&user_id, "custom.endpoint")
            .await?
        {
            allowed += 1;
        } else {
            break;
        }
    }

    println!("  Custom endpoint allowed: {}/30", allowed);
    assert!(
        allowed >= 18 && allowed <= 22,
        "Should use custom limit (20 req/s)"
    );

    Ok(())
}

#[tokio::test]
async fn test_trading_endpoint_high_capacity() -> Result<()> {
    println!("\n=== Test: Trading Endpoint High Capacity ===");

    let config = RateLimitConfig::trading_submit_order();

    assert_eq!(config.capacity, 100.0);
    assert_eq!(config.refill_rate, 100.0);
    assert_eq!(config.burst_size, 10);
    println!(
        "  ✓ Trading config: {} req/s, burst {}",
        config.refill_rate, config.burst_size
    );

    Ok(())
}

#[tokio::test]
async fn test_config_endpoint_low_capacity() -> Result<()> {
    println!("\n=== Test: Config Update Endpoint Low Capacity ===");

    let config = RateLimitConfig::config_update();

    assert_eq!(config.capacity, 10.0);
    assert_eq!(config.refill_rate, 10.0);
    assert_eq!(config.burst_size, 2);
    println!(
        "  ✓ Config update: {} req/s, burst {}",
        config.refill_rate, config.burst_size
    );

    Ok(())
}

#[tokio::test]
async fn test_backtesting_endpoint_very_low_rate() -> Result<()> {
    println!("\n=== Test: Backtesting Endpoint Very Low Rate ===");

    let config = RateLimitConfig::backtesting_run();

    assert_eq!(config.capacity, 5.0);
    assert!(config.refill_rate < 0.1); // 5 requests per minute
    assert_eq!(config.burst_size, 1);
    println!(
        "  ✓ Backtesting: {:.4} req/s (5 req/min), burst {}",
        config.refill_rate, config.burst_size
    );

    Ok(())
}

// ============================================================================
// Redis Integration Tests (5 tests)
// ============================================================================

#[tokio::test]
async fn test_redis_state_shared_across_instances() -> Result<()> {
    println!("\n=== Test: Redis State Sharing ===");

    let limiter1 = RateLimiter::new(REDIS_URL).await?;
    let limiter2 = RateLimiter::new(REDIS_URL).await?;

    let user_id = Uuid::new_v4();

    // Use first instance to exhaust tokens
    let mut count1 = 0;
    for _ in 0..10 {
        if limiter1.check_limit(&user_id, "config.update").await? {
            count1 += 1;
        } else {
            break;
        }
    }

    println!("  Instance 1 allowed: {}", count1);

    // Second instance should see same state
    let mut count2 = 0;
    for _ in 0..10 {
        if limiter2.check_limit(&user_id, "config.update").await? {
            count2 += 1;
        } else {
            break;
        }
    }

    println!("  Instance 2 allowed: {}", count2);

    // Total should not exceed capacity
    assert!(
        count1 + count2 <= 12,
        "Total should respect shared Redis state"
    );

    Ok(())
}

#[tokio::test]
async fn test_redis_lua_script_atomicity() -> Result<()> {
    println!("\n=== Test: Redis Lua Script Atomicity ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Launch 200 concurrent requests
    let mut handles = vec![];

    for _ in 0..200 {
        let limiter = rate_limiter.clone();
        let uid = user_id;

        let handle = tokio::spawn(async move { limiter.check_limit(&uid, "config.update").await });

        handles.push(handle);
    }

    // Collect results
    let mut allowed = 0;
    let mut denied = 0;

    for handle in handles {
        match handle.await? {
            Ok(true) => allowed += 1,
            Ok(false) => denied += 1,
            Err(_) => {},
        }
    }

    println!("  Allowed: {}, Denied: {}", allowed, denied);

    // Lua script should ensure exact limit (10 req/s for config.update)
    assert_eq!(allowed + denied, 200, "All requests should complete");
    assert!(
        allowed >= 9 && allowed <= 12,
        "Should allow ~10 requests atomically"
    );

    Ok(())
}

#[tokio::test]
async fn test_redis_key_ttl_set() -> Result<()> {
    println!("\n=== Test: Redis Key TTL (300s) ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;
    let user_id = Uuid::new_v4();

    // Make request to create Redis key
    let _ = rate_limiter
        .check_limit(&user_id, "trading.submit_order")
        .await?;

    println!("  ✓ Redis key created with 300s TTL");
    println!("  (Manual verification: redis-cli TTL ratelimit:...)");

    Ok(())
}

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

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;

    // Create 10 users
    let users: Vec<Uuid> = (0..10).map(|_| Uuid::new_v4()).collect();

    // Each user should have independent rate limit
    for (i, user_id) in users.iter().enumerate() {
        let mut allowed = 0;
        for _ in 0..15 {
            if rate_limiter.check_limit(user_id, "config.update").await? {
                allowed += 1;
            } else {
                break;
            }
        }

        println!("  User {} allowed: {}", i, allowed);
        assert!(
            allowed >= 9 && allowed <= 12,
            "Each user should have independent limit"
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_redis_connection_reuse() -> Result<()> {
    println!("\n=== Test: Redis Connection Pool Reuse ===");

    let rate_limiter = RateLimiter::new(REDIS_URL).await?;

    // Make 1000 requests to test connection pooling
    let mut handles = vec![];

    for _ in 0..1000 {
        let limiter = rate_limiter.clone();
        let user_id = Uuid::new_v4();

        let handle =
            tokio::spawn(
                async move { limiter.check_limit(&user_id, "trading.submit_order").await },
            );

        handles.push(handle);
    }

    // All should complete without errors
    let mut success = 0;
    let mut errors = 0;

    for handle in handles {
        match handle.await {
            Ok(Ok(_)) => success += 1,
            _ => errors += 1,
        }
    }

    println!("  Success: {}, Errors: {}", success, errors);
    assert_eq!(success + errors, 1000);
    assert_eq!(errors, 0, "Connection pool should handle all requests");

    Ok(())
}
