use std::path::Path;
use std::sync::Arc;

use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use libsql_sys::name::NamespaceName;
use libsql_sys::rusqlite::{self, OpenFlags};
use libsql_sys::wal::{Sqlite3Wal, Sqlite3WalManager, Wal};
use libsql_sys::Connection;
use libsql_wal::io::StdIO;
use libsql_wal::storage::NoStorage;
use libsql_wal::wal::LibsqlWal;
use libsql_wal::{registry::WalRegistry, wal::LibsqlWalManager};
use tempfile::tempdir;

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

pub fn criterion_benchmark(c: &mut Criterion) {
    with_libsql_conn(|conn| {
        c.bench_function("libsql random inserts", |b| {
            bench_random_inserts(conn, b);
        });
    });

    with_sqlite_conn(|conn| {
        c.bench_function("sqlite3 random inserts", |b| {
            bench_random_inserts(conn, b);
        });
    });

    with_sqlite_conn(|conn| {
        prepare_for_random_reads(conn);
        c.bench_function("sqlite3 random reads", |b| {
            bench_random_reads(conn, b);
        });
    });

    with_libsql_conn(|conn| {
        prepare_for_random_reads(conn);
        c.bench_function("libsql random reads", |b| {
            bench_random_reads(conn, b);
        });
    });
}

fn prepare_for_random_reads<W: Wal>(conn: &mut Connection<W>) {
    let _ = conn.execute(
        "CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB(16), c BLOB(16), d BLOB(400));",
        (),
    );
    let _ = conn.execute("CREATE INDEX i1 ON t1(b);", ());
    let _ = conn.execute("CREATE INDEX i2 ON t1(c);", ());
    for _ in 0..20_000 {
        random_inserts(conn);
    }
}

fn with_libsql_conn(f: impl FnOnce(&mut Connection<LibsqlWal<StdIO>>)) {
    let tmp = tempdir().unwrap();
    let resolver = |_: &Path| NamespaceName::from_string("test".into());

    let (sender, _) = tokio::sync::mpsc::channel(12);
    let registry = Arc::new(WalRegistry::new(NoStorage.into(), sender).unwrap());
    let wal_manager = LibsqlWalManager::new(registry.clone(), Arc::new(resolver));

    let mut conn = libsql_sys::Connection::open(
        tmp.path().join("data"),
        OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE,
        wal_manager.clone(),
        100000,
        None,
    )
    .unwrap();

    f(&mut conn)
}

fn with_sqlite_conn(f: impl FnOnce(&mut Connection<Sqlite3Wal>)) {
    let tmp = tempdir().unwrap();
    let mut conn = libsql_sys::Connection::open(
        tmp.path().join("data"),
        OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE,
        Sqlite3WalManager::default(),
        100000,
        None,
    )
    .unwrap();

    f(&mut conn)
}

fn bench_random_reads<W: Wal>(conn: &mut Connection<W>, bencher: &mut Bencher<'_>) {
    bencher.iter(|| random_read(conn));
}

fn bench_random_inserts<W: Wal>(conn: &mut Connection<W>, bencher: &mut Bencher<'_>) {
    let _ = conn.execute(
        "CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB(16), c BLOB(16), d BLOB(400));",
        (),
    );
    let _ = conn.execute("CREATE INDEX i1 ON t1(b);", ());
    let _ = conn.execute("CREATE INDEX i2 ON t1(c);", ());
    bencher.iter(|| random_inserts(conn));
}

fn random_inserts<W: Wal>(conn: &mut Connection<W>) {
    let tx = conn
        .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
        .unwrap();
    // println!("write_acquired: {:?}", before.elapsed().as_micros());
    tx.execute("REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));", ()).unwrap();
    tx.execute("REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));", ()).unwrap();
    tx.execute("REPLACE INTO t1 VALUES(abs(random() % 5000000), randomblob(16), randomblob(16), randomblob(400));", ()).unwrap();
    tx.commit().unwrap();
}

fn random_read<W: Wal>(conn: &mut Connection<W>) {
    let tx = conn.transaction().unwrap();
    // println!("write_acquired: {:?}", before.elapsed().as_micros());
    let mut stmt = tx
        .prepare("SELECT * FROM t1 WHERE a>abs((random()%5000000)) LIMIT 10;")
        .unwrap();
    stmt.query(()).unwrap().mapped(|_r| Ok(())).count();
    stmt.query(()).unwrap().mapped(|_r| Ok(())).count();
    stmt.query(()).unwrap().mapped(|_r| Ok(())).count();
    drop(stmt);
    tx.commit().unwrap();
}