//! M3 microbenchmark for `AllowlistAspect`: per-call overhead at the
//! authorization join point, isolated from any surrounding workload.
//!
//! Two questions the paper needs answered:
//!
//! 1. How much does `AllowlistAspect::is_allowed` cost compared to the
//!    pre-migration shape `allowed.iter().any(|u| u == "*" || u == id)`?
//!    The aspect adds an `Arc::clone`, a `RwLock::read`, and an
//!    `Option<Box<dyn Fn>>` dispatch on each call. Each of these has a
//!    measurable cost; we report the floor.
//!
//! 2. How does that cost scale with allowlist size? Linear scan is
//!    O(n); the aspect inherits that, but the per-call setup cost is
//!    constant and dominates for small N. We sweep N ∈ {1, 8, 64, 256}.
//!
//! Two regimes are benched per N:
//!   - `hit`  — the queried identity is in the list (last position, to
//!              defeat early-exit advantage). Worst case for scan.
//!   - `miss` — the queried identity is not in the list. Scans the
//!              entire list before returning false.
//!
//! For each regime the **baseline** (raw `Vec<String>` + closure) is
//! benched alongside the **aspect** (`AllowlistAspect`), so the
//! per-call delta is the framework cost and only that.

use aspect_std::AllowlistAspect;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::hint::black_box;

const SIZES: &[usize] = &[1, 8, 64, 256];

fn make_entries(n: usize) -> Vec<String> {
    (0..n).map(|i| format!("user_{i:08}")).collect()
}

#[inline]
fn baseline_is_allowed(entries: &[String], identity: &str) -> bool {
    entries.iter().any(|u| u == "*" || u == identity)
}

fn bench_allowlist(c: &mut Criterion) {
    let mut hit_group = c.benchmark_group("allowlist_hit");
    for &n in SIZES {
        let entries = make_entries(n);
        let target = entries.last().cloned().unwrap_or_else(|| "user_0".into());
        let aspect = AllowlistAspect::new(entries.clone());

        hit_group.bench_with_input(
            BenchmarkId::new("baseline_vec_any", n),
            &(entries.clone(), target.clone()),
            |b, (e, t)| {
                b.iter(|| baseline_is_allowed(black_box(e), black_box(t)));
            },
        );
        hit_group.bench_with_input(
            BenchmarkId::new("aspect_is_allowed", n),
            &(aspect.clone(), target.clone()),
            |b, (a, t)| {
                b.iter(|| a.is_allowed(black_box(t)));
            },
        );
    }
    hit_group.finish();

    let mut miss_group = c.benchmark_group("allowlist_miss");
    for &n in SIZES {
        let entries = make_entries(n);
        let target = "not_in_list".to_string();
        let aspect = AllowlistAspect::new(entries.clone());

        miss_group.bench_with_input(
            BenchmarkId::new("baseline_vec_any", n),
            &(entries.clone(), target.clone()),
            |b, (e, t)| {
                b.iter(|| baseline_is_allowed(black_box(e), black_box(t)));
            },
        );
        miss_group.bench_with_input(
            BenchmarkId::new("aspect_is_allowed", n),
            &(aspect.clone(), target.clone()),
            |b, (a, t)| {
                b.iter(|| a.is_allowed(black_box(t)));
            },
        );
    }
    miss_group.finish();

    // Wildcard short-circuit: the aspect must still pay one
    // `RwLock::read` + one `Arc::clone` to discover the "*", whereas
    // the baseline pays only the leading vector dereference. This
    // group isolates that delta.
    let mut wildcard_group = c.benchmark_group("allowlist_wildcard");
    {
        let entries = vec!["*".to_string()];
        let target = "anyone_at_all".to_string();
        let aspect = AllowlistAspect::new(entries.clone());

        wildcard_group.bench_function("baseline_vec_any", |b| {
            b.iter(|| baseline_is_allowed(black_box(&entries), black_box(&target)));
        });
        wildcard_group.bench_function("aspect_is_allowed", |b| {
            b.iter(|| aspect.is_allowed(black_box(&target)));
        });
    }
    wildcard_group.finish();
}

criterion_group!(benches, bench_allowlist);
criterion_main!(benches);
