use criterion::{Criterion, criterion_group, criterion_main};
use std::collections::HashMap as StdHashMap;
use std::hint::black_box;

use hashbrown::HashMap as BrownHashMap;

const SIZES: [usize; 3] = [5, 10, 50];
const QUERY_COUNT: usize = 4096;

/// Детерминированный ключ ~17 символов, например "key_9e3779b9_0042".
fn make_key(i: u64) -> String {
    let h = i.wrapping_mul(0x9E37_79B9_7F4A_7C15);
    format!("key_{:08x}_{:04}", h & 0xFFFF_FFFF, i)
}

// Для длинных ключей (пути/URL) замени на:
// fn make_key(i: u64) -> String {
//     format!(
//         "/api/v2/users/{i}/settings/visibility_{:08x}",
//         i.wrapping_mul(0x9E37_79B9)
//     )
// }

fn make_vec(n: usize) -> Vec<(String, u64)> {
    (0..n as u64).map(|i| (make_key(i), i)).collect()
}

fn make_std_map(data: &[(String, u64)]) -> StdHashMap<String, u64> {
    data.iter().map(|(k, v)| (k.clone(), *v)).collect()
}

fn make_brown_map(data: &[(String, u64)]) -> BrownHashMap<String, u64> {
    data.iter().map(|(k, v)| (k.clone(), *v)).collect()
}

fn make_queries(n: usize, with_misses: bool) -> Vec<String> {
    let keys: Vec<String> = (0..n as u64).map(make_key).collect();
    let missing = "__missing_key__".to_string();

    (0..QUERY_COUNT)
        .map(|i| {
            if with_misses && i % 16 == 15 {
                missing.clone()
            } else {
                let idx = i.wrapping_mul(2_654_435_761) % n;
                keys[idx].clone()
            }
        })
        .collect()
}

fn lookup_std(map: &StdHashMap<String, u64>, queries: &[String]) -> u64 {
    let mut sum = 0u64;
    for q in queries {
        match map.get(black_box(q.as_str())) {
            Some(v) => sum = sum.wrapping_add(*v),
            None => sum = sum.wrapping_add(1),
        }
    }
    black_box(sum)
}

fn lookup_brown(map: &BrownHashMap<String, u64>, queries: &[String]) -> u64 {
    let mut sum = 0u64;
    for q in queries {
        match map.get(black_box(q.as_str())) {
            Some(v) => sum = sum.wrapping_add(*v),
            None => sum = sum.wrapping_add(1),
        }
    }
    black_box(sum)
}

fn lookup_vec(data: &[(String, u64)], queries: &[String]) -> u64 {
    let mut sum = 0u64;
    for q in queries {
        let q = black_box(q);
        match data.iter().find(|(k, _)| k == q) {
            Some((_, v)) => sum = sum.wrapping_add(*v),
            None => sum = sum.wrapping_add(1),
        }
    }
    black_box(sum)
}

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

    for n in SIZES {
        let vec_data = make_vec(n);
        let std_map = make_std_map(&vec_data);
        let brown_map = make_brown_map(&vec_data);

        let queries_hit = make_queries(n, false);
        let queries_mixed = make_queries(n, true);

        // std::collections::HashMap (SipHash)
        group.bench_function(format!("std_hashmap_hit/{n}"), |b| {
            b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_hit)))
        });
        group.bench_function(format!("std_hashmap_mixed/{n}"), |b| {
            b.iter(|| lookup_std(black_box(&std_map), black_box(&queries_mixed)))
        });

        // hashbrown (SwissTable + foldhash)
        group.bench_function(format!("hashbrown_hit/{n}"), |b| {
            b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_hit)))
        });
        group.bench_function(format!("hashbrown_mixed/{n}"), |b| {
            b.iter(|| lookup_brown(black_box(&brown_map), black_box(&queries_mixed)))
        });

        // Vec<(String, u64)>, линейный поиск
        group.bench_function(format!("vec_linear_hit/{n}"), |b| {
            b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_hit)))
        });
        group.bench_function(format!("vec_linear_mixed/{n}"), |b| {
            b.iter(|| lookup_vec(black_box(&vec_data), black_box(&queries_mixed)))
        });
    }

    group.finish();
}

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