//! Revocation Cache Performance Benchmark
//!
//! Measures the performance impact of local revocation cache:
//! - Cache hit latency: TARGET <10ns
//! - Cache miss latency: ~500μs (Redis network)
//! - Cache hit rate: TARGET >95%
//! - Memory overhead: Minimal with TTL expiration
//!
//! This benchmark compares:
//! 1. Direct Redis calls (no cache)
//! 2. Local DashMap cache with 60s TTL
//! 3. Cache behavior under different access patterns

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};

/// Simulated revocation result
#[derive(Debug, Clone)]
struct CachedRevocationResult {
    is_revoked: bool,
    cached_at: Instant,
}

/// Local revocation cache implementation
struct LocalRevocationCache {
    cache: Arc<DashMap<String, CachedRevocationResult>>,
    ttl: Duration,
    hits: Arc<std::sync::atomic::AtomicU64>,
    misses: Arc<std::sync::atomic::AtomicU64>,
}

impl LocalRevocationCache {
    fn new(ttl: Duration) -> Self {
        Self {
            cache: Arc::new(DashMap::new()),
            ttl,
            hits: Arc::new(std::sync::atomic::AtomicU64::new(0)),
            misses: Arc::new(std::sync::atomic::AtomicU64::new(0)),
        }
    }

    fn check_revoked(&self, token_id: &str, simulate_redis_latency: bool) -> bool {
        // Check cache first
        if let Some(entry) = self.cache.get(token_id) {
            if entry.cached_at.elapsed() < self.ttl {
                // Cache hit
                self.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                return entry.is_revoked;
            } else {
                // Expired - remove it
                drop(entry);
                self.cache.remove(token_id);
            }
        }

        // Cache miss - simulate Redis lookup
        self.misses
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);

        if simulate_redis_latency {
            // Simulate 500μs Redis network latency
            std::thread::sleep(Duration::from_micros(500));
        }

        // Simulate Redis result (99% not revoked in production)
        let is_revoked = token_id.contains("revoked");

        // Update cache
        self.cache.insert(
            token_id.to_string(),
            CachedRevocationResult {
                is_revoked,
                cached_at: Instant::now(),
            },
        );

        is_revoked
    }

    fn stats(&self) -> (u64, u64, f64) {
        let hits = self.hits.load(std::sync::atomic::Ordering::Relaxed);
        let misses = self.misses.load(std::sync::atomic::Ordering::Relaxed);
        let total = hits + misses;
        let hit_rate = if total > 0 {
            (hits as f64 / total as f64) * 100.0
        } else {
            0.0
        };
        (hits, misses, hit_rate)
    }
}

/// Benchmark 1: Cache hit latency (TARGET: <10ns)
fn bench_cache_hit_latency(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    // Prepopulate cache with 1000 tokens
    for i in 0..1000 {
        let token_id = format!("token_{}", i);
        cache.check_revoked(&token_id, false); // Prime cache without latency
    }

    c.bench_function("revocation_cache_hit", |b| {
        b.iter(|| {
            let token_id = format!("token_{}", black_box(500));
            let is_revoked = cache.check_revoked(&token_id, false);
            black_box(is_revoked);
        });
    });
}

/// Benchmark 2: Cache miss latency (with simulated Redis)
fn bench_cache_miss_latency(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    c.bench_function("revocation_cache_miss_with_redis", |b| {
        let mut counter = 0;
        b.iter(|| {
            let token_id = format!("new_token_{}", black_box(counter));
            let is_revoked = cache.check_revoked(&token_id, true); // Simulate Redis latency
            black_box(is_revoked);
            counter += 1;
        });
    });
}

/// Benchmark 3: Hot token pattern (95% cache hit rate)
fn bench_hot_token_pattern(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    // Prepopulate with hot tokens
    for i in 0..10 {
        let token_id = format!("hot_token_{}", i);
        cache.check_revoked(&token_id, false);
    }

    c.bench_function("hot_token_pattern_95pct_hits", |b| {
        let mut counter = 0;
        b.iter(|| {
            // 95% hits (tokens 0-9), 5% misses (tokens 10-19)
            let token_num = if counter % 20 < 19 {
                counter % 10
            } else {
                10 + (counter % 10)
            };
            let token_id = format!("hot_token_{}", black_box(token_num));
            let is_revoked = cache.check_revoked(&token_id, false);
            black_box(is_revoked);
            counter += 1;
        });
    });

    // Report statistics
    let (hits, misses, hit_rate) = cache.stats();
    println!(
        "\nHot token pattern stats: {} hits, {} misses, {:.2}% hit rate",
        hits, misses, hit_rate
    );
}

/// Benchmark 4: TTL expiration behavior
fn bench_ttl_expiration(c: &mut Criterion) {
    let mut group = c.benchmark_group("ttl_expiration");

    // Short TTL (1ms) - high expiration rate
    let cache_short = LocalRevocationCache::new(Duration::from_millis(1));
    for i in 0..100 {
        cache_short.check_revoked(&format!("token_{}", i), false);
    }

    group.bench_function("1ms_ttl", |b| {
        b.iter(|| {
            let token_id = format!("token_{}", black_box(50));
            let is_revoked = cache_short.check_revoked(&token_id, false);
            black_box(is_revoked);
        });
    });

    // Long TTL (60s) - low expiration rate
    let cache_long = LocalRevocationCache::new(Duration::from_secs(60));
    for i in 0..100 {
        cache_long.check_revoked(&format!("token_{}", i), false);
    }

    group.bench_function("60s_ttl", |b| {
        b.iter(|| {
            let token_id = format!("token_{}", black_box(50));
            let is_revoked = cache_long.check_revoked(&token_id, false);
            black_box(is_revoked);
        });
    });

    group.finish();
}

/// Benchmark 5: Cache size impact
fn bench_cache_size_impact(c: &mut Criterion) {
    let mut group = c.benchmark_group("cache_size_impact");

    for size in &[100, 1_000, 10_000, 100_000] {
        let cache = LocalRevocationCache::new(Duration::from_secs(60));

        // Prepopulate to target size
        for i in 0..*size {
            cache.check_revoked(&format!("token_{}", i), false);
        }

        group.bench_with_input(BenchmarkId::new("lookup", size), size, |b, &n| {
            b.iter(|| {
                let token_id = format!("token_{}", black_box(n / 2));
                let is_revoked = cache.check_revoked(&token_id, false);
                black_box(is_revoked);
            });
        });
    }

    group.finish();
}

/// Benchmark 6: Concurrent access pattern
fn bench_concurrent_access(c: &mut Criterion) {
    let cache = Arc::new(LocalRevocationCache::new(Duration::from_secs(60)));

    // Prepopulate
    for i in 0..1000 {
        cache.check_revoked(&format!("token_{}", i), false);
    }

    c.bench_function("concurrent_cache_access", |b| {
        b.iter(|| {
            let cache_clone = cache.clone();
            let token_id = format!("token_{}", black_box(500));
            let is_revoked = cache_clone.check_revoked(&token_id, false);
            black_box(is_revoked);
        });
    });
}

/// Benchmark 7: Mixed revoked/valid token pattern
fn bench_mixed_revocation(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    // Prepopulate with mix of revoked and valid tokens
    for i in 0..1000 {
        let token_id = if i % 10 == 0 {
            format!("revoked_token_{}", i) // 10% revoked
        } else {
            format!("valid_token_{}", i) // 90% valid
        };
        cache.check_revoked(&token_id, false);
    }

    c.bench_function("mixed_revocation_pattern", |b| {
        let mut counter = 0;
        b.iter(|| {
            let token_id = if counter % 10 == 0 {
                format!("revoked_token_{}", black_box(counter))
            } else {
                format!("valid_token_{}", black_box(counter))
            };
            let is_revoked = cache.check_revoked(&token_id, false);
            black_box(is_revoked);
            counter = (counter + 1) % 1000;
        });
    });
}

/// Benchmark 8: Cache vs no-cache comparison
fn bench_cache_vs_no_cache(c: &mut Criterion) {
    let mut group = c.benchmark_group("cache_vs_no_cache");

    // No cache - direct Redis simulation
    group.bench_function("no_cache_direct_redis", |b| {
        b.iter(|| {
            // Simulate 500μs Redis latency every time
            std::thread::sleep(Duration::from_micros(500));
            let is_revoked = false; // Simulate result
            black_box(is_revoked);
        });
    });

    // With cache - 95% hit rate
    let cache = LocalRevocationCache::new(Duration::from_secs(60));
    for i in 0..10 {
        cache.check_revoked(&format!("token_{}", i), false);
    }

    group.bench_function("with_cache_95pct_hits", |b| {
        let mut counter = 0;
        b.iter(|| {
            let token_num = if counter % 20 < 19 {
                counter % 10 // Hit
            } else {
                10 + (counter % 10) // Miss
            };
            let token_id = format!("token_{}", black_box(token_num));
            let is_revoked = cache.check_revoked(&token_id, counter % 20 >= 19); // Only simulate latency on misses
            black_box(is_revoked);
            counter += 1;
        });
    });

    group.finish();
}

/// Benchmark 9: Memory overhead measurement
fn bench_memory_overhead(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    c.bench_function("cache_entry_insertion", |b| {
        let mut counter = 0;
        b.iter(|| {
            let token_id = format!("token_{}", black_box(counter));
            cache.check_revoked(&token_id, false);
            counter += 1;
        });
    });
}

/// Benchmark 10: Realistic production workload
fn bench_production_workload(c: &mut Criterion) {
    let cache = LocalRevocationCache::new(Duration::from_secs(60));

    // Simulate production: 1000 active users, 95% cache hit rate, 1% revoked
    for i in 0..1000 {
        let token_id = if i % 100 == 0 {
            format!("revoked_token_{}", i)
        } else {
            format!("valid_token_{}", i)
        };
        cache.check_revoked(&token_id, false);
    }

    c.bench_function("production_workload_simulation", |b| {
        let mut counter = 0;
        b.iter(|| {
            // 95% hits to existing tokens, 5% new tokens
            let token_id = if counter % 20 < 19 {
                // Cache hit - existing token
                let idx = counter % 1000;
                if idx % 100 == 0 {
                    format!("revoked_token_{}", idx)
                } else {
                    format!("valid_token_{}", idx)
                }
            } else {
                // Cache miss - new token
                format!("new_token_{}", counter)
            };

            let simulate_latency = counter % 20 >= 19;
            let is_revoked = cache.check_revoked(&token_id, simulate_latency);
            black_box(is_revoked);
            counter += 1;
        });
    });

    // Report final statistics
    let (hits, misses, hit_rate) = cache.stats();
    println!(
        "\nProduction workload stats: {} hits, {} misses, {:.2}% hit rate",
        hits, misses, hit_rate
    );
}

criterion_group!(
    revocation_cache_benches,
    bench_cache_hit_latency,
    bench_cache_miss_latency,
    bench_hot_token_pattern,
    bench_ttl_expiration,
    bench_cache_size_impact,
    bench_concurrent_access,
    bench_mixed_revocation,
    bench_cache_vs_no_cache,
    bench_memory_overhead,
    bench_production_workload
);

criterion_main!(revocation_cache_benches);
