use std::time::{Duration, Instant, SystemTime};

use criterion::{
    criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput,
};

use kanidmd_lib::entry::{Entry, EntryInit, EntryNew};
use kanidmd_lib::entry_init;
use kanidmd_lib::prelude::{Attribute, EntryClass};
use kanidmd_lib::testkit::{setup_idm_test, TestConfiguration};
use kanidmd_lib::value::Value;

pub fn duration_from_epoch_now() -> Duration {
    #[allow(clippy::expect_used)]
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .expect("invalid duration from epoch now")
}

pub fn scaling_user_create_single(c: &mut Criterion) {
    let mut group = c.benchmark_group("user_create_single");
    group.sample_size(10);
    group.sampling_mode(SamplingMode::Flat);
    group.warm_up_time(Duration::from_secs(5));
    group.measurement_time(Duration::from_secs(120));

    for size in &[100, 250, 500, 1000, 1500, 2000, 5000, 10000] {
        group.throughput(Throughput::Elements(*size));
        group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
            b.iter_custom(|iters| {
                let mut elapsed = Duration::from_secs(0);
                println!("iters, size -> {iters:?}, {size:?}");

                for _i in 0..iters {
                    let mut rt = tokio::runtime::Builder::new_current_thread();
                    elapsed = rt
                        .enable_all()
                        .build()
                        .expect("Failed building the Runtime")
                        .block_on(async {
                            let (idms, _idms_delayed, _idms_audit) =
                                setup_idm_test(TestConfiguration::default()).await;

                            let ct = duration_from_epoch_now();
                            let start = Instant::now();
                            for counter in 0..size {
                                let mut idms_prox_write =
                                    idms.proxy_write(ct).await.expect("Failed to get write txn");
                                let name = format!("testperson_{counter}");
                                let e1 = entry_init!(
                                    (Attribute::Class, EntryClass::Object.to_value()),
                                    (Attribute::Class, EntryClass::Person.to_value()),
                                    (Attribute::Class, EntryClass::Account.to_value()),
                                    (Attribute::Name, Value::new_iname(&name)),
                                    (Attribute::Description, Value::new_utf8s("criterion")),
                                    (Attribute::DisplayName, Value::new_utf8s(&name))
                                );

                                let cr = idms_prox_write.qs_write.internal_create(vec![e1]);
                                assert!(cr.is_ok());

                                idms_prox_write.commit().expect("Must not fail");
                            }
                            elapsed.checked_add(start.elapsed()).unwrap()
                        });
                }
                elapsed
            });
        });
    }
    group.finish();
}

pub fn scaling_user_create_batched(c: &mut Criterion) {
    let mut group = c.benchmark_group("user_create_batched");
    group.sample_size(10);
    group.sampling_mode(SamplingMode::Flat);
    group.warm_up_time(Duration::from_secs(5));
    group.measurement_time(Duration::from_secs(120));

    for size in &[100, 250, 500, 1000, 1500, 2000, 5000, 10000] {
        group.throughput(Throughput::Elements(*size));
        group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
            b.iter_custom(|iters| {
                let mut elapsed = Duration::from_secs(0);
                println!("iters, size -> {iters:?}, {size:?}");

                let data: Vec<_> = (0..size)
                    .map(|i| {
                        let name = format!("testperson_{i}");
                        entry_init!(
                            (Attribute::Class, EntryClass::Object.to_value()),
                            (Attribute::Class, EntryClass::Person.to_value()),
                            (Attribute::Class, EntryClass::Account.to_value()),
                            (Attribute::Name, Value::new_iname(&name)),
                            (Attribute::Description, Value::new_utf8s("criterion")),
                            (Attribute::DisplayName, Value::new_utf8s(&name))
                        )
                    })
                    .collect();

                for _i in 0..iters {
                    let mut rt = tokio::runtime::Builder::new_current_thread();
                    elapsed = rt
                        .enable_all()
                        .build()
                        .expect("Failed building the Runtime")
                        .block_on(async {
                            let (idms, _idms_delayed, _idms_audit) =
                                setup_idm_test(TestConfiguration::default()).await;

                            let ct = duration_from_epoch_now();
                            let start = Instant::now();

                            let mut idms_prox_write =
                                idms.proxy_write(ct).await.expect("Failed to get write txn");
                            let cr = idms_prox_write.qs_write.internal_create(data.clone());
                            assert!(cr.is_ok());

                            idms_prox_write.commit().expect("Must not fail");
                            elapsed.checked_add(start.elapsed()).unwrap()
                        });
                }
                elapsed
            });
        });
    }
    group.finish();
}

criterion_group!(
    name = scaling_basic;
    config = Criterion::default()
        .measurement_time(Duration::from_secs(15))
        .with_plots();
    targets = scaling_user_create_single, scaling_user_create_batched
);
criterion_main!(scaling_basic);