#![allow(missing_docs)]
#![allow(clippy::float_cmp)]
#![allow(clippy::cast_possible_truncation)]

#![allow(clippy::cast_precision_loss)]
//! Cache (TLB) performance benchmarks
//!
//! Benchmarks for measuring TLB hit rates, invalidation performance,
//! and cache efficiency

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use std::time::Duration;

// ============================================================================
// Benchmark Configuration
// ============================================================================

fn configure_criterion() -> Criterion {
    Criterion::default()
        .warm_up_time(Duration::from_secs(2))
        .measurement_time(Duration::from_secs(5))
        .sample_size(500)
}

// ============================================================================
// TLB Hit/Miss Benchmarks
// ============================================================================

fn bench_tlb_hit(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};

    // Setup: Create cache and populate with entries
    let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
    let stream_id = StreamID::new(1).unwrap();
    let pasid = PASID::new(0).unwrap();

    // Pre-populate cache with 100 entries
    for page in 0..100 {
        let iova = IOVA::new(page * 0x1000).unwrap();
        let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
        let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
        let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
        cache.insert(key, entry);
    }

    // Benchmark: Lookup existing entry (hit)
    let lookup_key = CacheKey::new(stream_id, pasid, IOVA::new(50 * 0x1000).unwrap(), SecurityState::NonSecure);

    c.bench_function("tlb_hit", |b| {
        b.iter(|| {
            let result = cache.lookup(black_box(&lookup_key));
            black_box(result);
        });
    });
}

fn bench_tlb_miss(c: &mut Criterion) {
    use smmu::cache::{CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{SecurityState, StreamID, IOVA, PASID};

    // Setup: Create empty cache (no entries)
    let cache = TlbCache::new(1024, ReplacementPolicy::Lru);

    // Lookup key that doesn't exist in cache (guaranteed miss)
    let lookup_key = CacheKey::new(
        StreamID::new(1).unwrap(),
        PASID::new(0).unwrap(),
        IOVA::new(0x1000).unwrap(),
        SecurityState::NonSecure,
    );

    c.bench_function("tlb_miss", |b| {
        b.iter(|| {
            let result = cache.lookup(black_box(&lookup_key));
            black_box(result);
        });
    });
}

fn bench_tlb_hit_rate(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};

    let mut group = c.benchmark_group("tlb_hit_rate");

    // Test with different working set sizes
    for num_pages in &[10, 100, 1000, 10_000] {
        group.bench_with_input(BenchmarkId::from_parameter(num_pages), num_pages, |b, &num_pages| {
            let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
            let stream_id = StreamID::new(1).unwrap();
            let pasid = PASID::new(0).unwrap();

            // Populate cache with 1024 entries
            for page in 0..1024 {
                let iova = IOVA::new(page * 0x1000).unwrap();
                let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
                let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
                let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
                cache.insert(key, entry);
            }

            // Access pattern: cycle through num_pages
            // Small working sets should have high hit rates
            b.iter(|| {
                for page in 0..num_pages {
                    let iova = IOVA::new((page % num_pages) * 0x1000).unwrap();
                    let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
                    black_box(cache.lookup(&key));
                }
            });
        });
    }

    group.finish();
}

// ============================================================================
// TLB Invalidation Benchmarks
// ============================================================================

fn bench_tlb_invalidate_all(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};

    c.bench_function("tlb_invalidate_all", |b| {
        b.iter_batched(
            || {
                // Setup: Create and populate cache
                let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
                let stream_id = StreamID::new(1).unwrap();
                let pasid = PASID::new(0).unwrap();

                for page in 0..1024 {
                    let iova = IOVA::new(page * 0x1000).unwrap();
                    let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
                    let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
                    let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
                    cache.insert(key, entry);
                }

                cache
            },
            |cache| {
                // Benchmark: Invalidate all entries
                cache.invalidate_all();
            },
            criterion::BatchSize::SmallInput,
        );
    });
}

fn bench_tlb_invalidate_by_stream(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};

    c.bench_function("tlb_invalidate_by_stream", |b| {
        b.iter_batched(
            || {
                // Setup: Create cache with multiple streams
                let cache = TlbCache::new(1024, ReplacementPolicy::Lru);

                // Populate with 10 streams, 100 entries each
                for stream in 0..10 {
                    let stream_id = StreamID::new(stream).unwrap();
                    let pasid = PASID::new(0).unwrap();

                    for page in 0..100 {
                        let iova = IOVA::new(page * 0x1000).unwrap();
                        let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
                        let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
                        let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
                        cache.insert(key, entry);
                    }
                }

                (cache, StreamID::new(5).unwrap())
            },
            |(cache, stream_id)| {
                // Benchmark: Invalidate single stream
                cache.invalidate_by_stream(stream_id);
            },
            criterion::BatchSize::SmallInput,
        );
    });
}

fn bench_tlb_invalidate_by_pasid(c: &mut Criterion) {
    c.bench_function("tlb_invalidate_by_pasid", |b| {
        b.iter(|| {
            // TODO: Selective invalidation by PASID
            black_box(());
        });
    });
}

fn bench_tlb_invalidate_by_va(c: &mut Criterion) {
    c.bench_function("tlb_invalidate_by_va", |b| {
        b.iter(|| {
            // TODO: Selective invalidation by VA range
            black_box(());
        });
    });
}

// ============================================================================
// Cache Size Impact Benchmarks
// ============================================================================

fn bench_tlb_size_impact(c: &mut Criterion) {
    let mut group = c.benchmark_group("tlb_size_impact");

    // Test with different TLB sizes
    for tlb_entries in &[64, 128, 256, 512, 1024] {
        group.bench_with_input(BenchmarkId::from_parameter(tlb_entries), tlb_entries, |b, &tlb_entries| {
            b.iter(|| {
                // TODO: Measure performance with different TLB sizes
                // Larger TLB = better hit rate for larger working sets
                black_box(tlb_entries);
            });
        });
    }

    group.finish();
}

// ============================================================================
// Replacement Policy Benchmarks
// ============================================================================

fn bench_tlb_lru_replacement(c: &mut Criterion) {
    c.bench_function("tlb_lru_replacement", |b| {
        b.iter(|| {
            // TODO: Measure LRU replacement performance
            black_box(());
        });
    });
}

fn bench_tlb_fifo_replacement(c: &mut Criterion) {
    c.bench_function("tlb_fifo_replacement", |b| {
        b.iter(|| {
            // TODO: Measure FIFO replacement performance
            black_box(());
        });
    });
}

// ============================================================================
// Access Pattern Benchmarks
// ============================================================================

fn bench_sequential_access_pattern(c: &mut Criterion) {
    c.bench_function("sequential_access_pattern", |b| {
        b.iter(|| {
            // TODO: Sequential VA access pattern
            // Should have good TLB hit rate
            black_box(());
        });
    });
}

fn bench_random_access_pattern(c: &mut Criterion) {
    c.bench_function("random_access_pattern", |b| {
        b.iter(|| {
            // TODO: Random VA access pattern
            // Lower TLB hit rate, stress test
            black_box(());
        });
    });
}

fn bench_strided_access_pattern(c: &mut Criterion) {
    let mut group = c.benchmark_group("strided_access_pattern");

    // Test with different stride sizes
    for stride in &[1, 2, 4, 8, 16] {
        group.bench_with_input(BenchmarkId::from_parameter(stride), stride, |b, &stride| {
            b.iter(|| {
                // TODO: Strided access pattern (every Nth page)
                black_box(stride);
            });
        });
    }

    group.finish();
}

// ============================================================================
// Multi-Level Cache Benchmarks
// ============================================================================

fn bench_l1_tlb_performance(c: &mut Criterion) {
    c.bench_function("l1_tlb_performance", |b| {
        b.iter(|| {
            // TODO: L1 TLB performance (if multi-level)
            black_box(());
        });
    });
}

fn bench_l2_tlb_performance(c: &mut Criterion) {
    c.bench_function("l2_tlb_performance", |b| {
        b.iter(|| {
            // TODO: L2 TLB performance (if multi-level)
            black_box(());
        });
    });
}

// ============================================================================
// Concurrent Access Benchmarks
// ============================================================================

fn bench_concurrent_tlb_access(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};
    use std::sync::Arc;
    use std::thread;

    c.bench_function("concurrent_tlb_access", |b| {
        let cache = Arc::new(TlbCache::new(1024, ReplacementPolicy::Lru));
        let stream_id = StreamID::new(1).unwrap();
        let pasid = PASID::new(0).unwrap();

        // Pre-populate cache
        for page in 0..100 {
            let iova = IOVA::new(page * 0x1000).unwrap();
            let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
            let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
            let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
            cache.insert(key, entry);
        }

        b.iter(|| {
            let mut handles = vec![];

            // Spawn 4 threads performing lookups
            for thread_id in 0..4 {
                let cache_clone = Arc::clone(&cache);
                let handle = thread::spawn(move || {
                    for _ in 0..100 {
                        let page = (thread_id * 25) % 100;
                        let iova = IOVA::new(page * 0x1000).unwrap();
                        let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
                        black_box(cache_clone.lookup(&key));
                    }
                });
                handles.push(handle);
            }

            // Wait for all threads
            for handle in handles {
                handle.join().unwrap();
            }
        });
    });
}

fn bench_concurrent_invalidation(c: &mut Criterion) {
    c.bench_function("concurrent_invalidation", |b| {
        b.iter(|| {
            // TODO: Invalidation during lookups
            // Test synchronization overhead
            black_box(());
        });
    });
}

// ============================================================================
// Cache Efficiency Metrics
// ============================================================================

fn bench_cache_overhead(c: &mut Criterion) {
    c.bench_function("cache_overhead", |b| {
        b.iter(|| {
            // TODO: Measure memory overhead of cache structures
            black_box(());
        });
    });
}

fn bench_cache_insertion_cost(c: &mut Criterion) {
    c.bench_function("cache_insertion_cost", |b| {
        b.iter(|| {
            // TODO: Measure cost of adding entry to cache
            black_box(());
        });
    });
}

fn bench_cache_eviction_cost(c: &mut Criterion) {
    c.bench_function("cache_eviction_cost", |b| {
        b.iter(|| {
            // TODO: Measure cost of evicting entry from cache
            black_box(());
        });
    });
}

// ============================================================================
// Comparison Benchmarks
// ============================================================================

fn bench_cache_comparison(c: &mut Criterion) {
    use smmu::cache::{CacheEntry, CacheKey, ReplacementPolicy, TlbCache};
    use smmu::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};

    let mut group = c.benchmark_group("cache_comparison");

    // Benchmark with warm cache
    group.bench_function("warm_cache", |b| {
        let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
        let stream_id = StreamID::new(1).unwrap();
        let pasid = PASID::new(0).unwrap();

        // Pre-populate cache
        for page in 0..100 {
            let iova = IOVA::new(page * 0x1000).unwrap();
            let pa = PA::new(page * 0x1000 + 0x1_0000).unwrap();
            let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
            let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), page);
            cache.insert(key, entry);
        }

        let lookup_key = CacheKey::new(stream_id, pasid, IOVA::new(50 * 0x1000).unwrap(), SecurityState::NonSecure);

        b.iter(|| {
            let result = cache.lookup(black_box(&lookup_key));
            black_box(result);
        });
    });

    // Benchmark with cold cache (miss)
    group.bench_function("cold_cache", |b| {
        let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
        let lookup_key = CacheKey::new(
            StreamID::new(1).unwrap(),
            PASID::new(0).unwrap(),
            IOVA::new(0x1000).unwrap(),
            SecurityState::NonSecure,
        );

        b.iter(|| {
            let result = cache.lookup(black_box(&lookup_key));
            black_box(result);
        });
    });

    // Benchmark without cache (simulated page table walk)
    group.bench_function("without_cache", |b| {
        // Simulate page table walk with some computation
        b.iter(|| {
            let va = black_box(0x1000u64);
            let level3_idx = (va >> 12) & 0x1FF;
            let level2_idx = (va >> 21) & 0x1FF;
            let level1_idx = (va >> 30) & 0x1FF;
            let level0_idx = (va >> 39) & 0x1FF;

            // Simulate 4-level page table walk
            let pa = (level0_idx << 39) | (level1_idx << 30) | (level2_idx << 21) | (level3_idx << 12);
            black_box(pa);
        });
    });

    group.finish();
}

// ============================================================================
// Benchmark Groups
// ============================================================================

criterion_group! {
    name = benches;
    config = configure_criterion();
    targets =
        bench_tlb_hit,
        bench_tlb_miss,
        bench_tlb_hit_rate,
        bench_tlb_invalidate_all,
        bench_tlb_invalidate_by_stream,
        bench_tlb_invalidate_by_pasid,
        bench_tlb_invalidate_by_va,
        bench_tlb_size_impact,
        bench_tlb_lru_replacement,
        bench_tlb_fifo_replacement,
        bench_sequential_access_pattern,
        bench_random_access_pattern,
        bench_strided_access_pattern,
        bench_l1_tlb_performance,
        bench_l2_tlb_performance,
        bench_concurrent_tlb_access,
        bench_concurrent_invalidation,
        bench_cache_overhead,
        bench_cache_insertion_cost,
        bench_cache_eviction_cost,
        bench_cache_comparison
}

criterion_main!(benches);
