use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
use std::time::Duration;
use surrealdb::dbs::Session;
use surrealdb::kvs::Datastore;
use surrealdb::sql::Value;
use tokio::runtime::Runtime;

fn bench_processor(c: &mut Criterion) {
	let rt = tokio::runtime::Runtime::new().unwrap();
	let i = rt.block_on(prepare_data());

	let mut group = c.benchmark_group("processor");
	group.throughput(Throughput::Elements(1));
	group.sample_size(10);
	group.measurement_time(Duration::from_secs(15));

	group.bench_function("table-iterator", |b| {
		b.to_async(Runtime::new().unwrap()).iter(|| run(&i, "SELECT * FROM item", i.count * 5))
	});

	group.bench_function("table-iterator-parallel", |b| {
		b.to_async(Runtime::new().unwrap())
			.iter(|| run(&i, "SELECT * FROM item PARALLEL", i.count * 5))
	});

	group.bench_function("non-uniq-index-iterator", |b| {
		b.to_async(Runtime::new().unwrap())
			.iter(|| run(&i, "SELECT * FROM item WHERE number=4", i.count))
	});

	group.bench_function("non-uniq-index-iterator-parallel", |b| {
		b.to_async(Runtime::new().unwrap())
			.iter(|| run(&i, "SELECT * FROM item WHERE number=4 PARALLEL", i.count))
	});

	group.bench_function("full-text-index-iterator", |b| {
		b.to_async(Runtime::new().unwrap())
			.iter(|| run(&i, "SELECT * FROM item WHERE label @@ 'charlie'", i.count))
	});

	group.bench_function("full-text-index-iterator-parallel", |b| {
		b.to_async(Runtime::new().unwrap())
			.iter(|| run(&i, "SELECT * FROM item WHERE label @@ 'charlie' PARALLEL", i.count))
	});

	group.finish();
}

struct Input {
	dbs: Datastore,
	ses: Session,
	count: usize,
}

async fn prepare_data() -> Input {
	let dbs = Datastore::new("memory").await.unwrap();
	let ses = Session::owner().with_ns("bench").with_db("bench");
	let sql = r"DEFINE INDEX number ON item FIELDS number;
		DEFINE ANALYZER simple TOKENIZERS blank,class;
		DEFINE INDEX search ON item FIELDS label SEARCH ANALYZER simple BM25"
		.to_owned();
	let res = &mut dbs.execute(&sql, &ses, None).await.unwrap();
	for _ in 0..3 {
		assert!(res.remove(0).result.is_ok());
	}

	let count = if cfg!(debug_assertions) {
		100 // debug is much slower!
	} else {
		10_000
	};

	for i in 0..count {
		let j = i * 5;
		let a = j;
		let b = j + 1;
		let c = j + 2;
		let d = j + 3;
		let e = j + 4;
		let sql = format!(
			r"CREATE item SET id = {a}, name = '{a}', number = 0, label='alpha';
		CREATE item SET id = {b}, name = '{b}', number = 1, label='bravo';
		CREATE item SET id = {c}, name = '{c}', number = 2, label='charlie';
		CREATE item SET id = {d}, name = '{d}', number = 3, label='delta';
		CREATE item SET id = {e}, name = '{e}', number = 4, label='echo';",
		);
		let res = &mut dbs.execute(&sql, &ses, None).await.unwrap();
		for _ in 0..5 {
			assert!(res.remove(0).result.is_ok());
		}
	}
	Input {
		dbs,
		ses,
		count,
	}
}

async fn run(i: &Input, q: &str, expected: usize) {
	let mut r = i.dbs.execute(black_box(q), &i.ses, None).await.unwrap();
	if cfg!(debug_assertions) {
		assert_eq!(r.len(), 1);
		if let Value::Array(a) = r.remove(0).result.unwrap() {
			assert_eq!(a.len(), expected);
		} else {
			panic!("Fail");
		}
	}
	black_box(r);
}

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