use std::ffi::CString;

use coz;
use mysql::{TxOpts, prelude::Queryable};
use pyo3::{ffi::c_str, prelude::*};

fn setup_db() {
    let mut conn = mysql::Conn::new("mysql://test:1234@127.0.0.1:3306/test").unwrap();
    conn.exec_drop("DROP TABLE IF EXISTS benchmark_test", ())
        .unwrap();
    conn.exec_drop(
        "CREATE TABLE benchmark_test (
            id INT PRIMARY KEY AUTO_INCREMENT,
            name VARCHAR(100),
            age INT,
            email VARCHAR(100),
            score FLOAT,
            description VARCHAR(100)
        ) ENGINE = MEMORY",
        (),
    )
    .unwrap();
}

fn clear_table() {
    let mut conn = mysql::Conn::new("mysql://test:1234@127.0.0.1:3306/test").unwrap();
    conn.exec_drop("TRUNCATE TABLE benchmark_test", ()).unwrap();
}

fn populate_table(n: usize) {
    let mut conn = mysql::Conn::new("mysql://test:1234@127.0.0.1:3306/test").unwrap();
    conn.exec_drop("TRUNCATE TABLE benchmark_test", ()).unwrap();
    {
        let mut tx = conn.start_transaction(TxOpts::default()).unwrap();
        for i in 0..n {
            tx.exec_drop(
                "INSERT INTO benchmark_test (name, age, email, score, description)
                       VALUES (?, ?, ?, ?, ?)",
                (
                    format!("user_{i}"),
                    20 + (i % 50),
                    format!("user{i}@example.com"),
                    (i % 100) as f32,
                    format!("User description {i}"),
                ),
            )
            .unwrap();
        }
        tx.commit().unwrap();
    }
}

fn bench_select_pyro_sync(iterations: usize, select_size: usize) {
    populate_table(iterations);
    let statement = CString::new(format!("select_pyro_sync({iterations}, {select_size})")).unwrap();
    Python::attach(|py| {
        py.eval(&statement, None, None).unwrap();
    });
    coz::progress!("select-iteration");
}

fn bench_select_pyro_async(iterations: usize, select_size: usize) {
    populate_table(iterations);
    let statement = CString::new(format!(
        "loop.run_until_complete(select_pyro_async({iterations}, {select_size}))"
    ))
    .unwrap();
    Python::attach(|py| {
        py.eval(&statement, None, None).unwrap();
    });
    coz::progress!("select-iteration");
}

fn bench_insert_pyro_sync(iterations: usize) {
    clear_table();
    let statement = CString::new(format!("insert_pyro_sync({iterations})")).unwrap();
    Python::attach(|py| {
        py.eval(&statement, None, None).unwrap();
    });
    coz::progress!("insert-iteration");
}

fn bench_insert_pyro_async(iterations: usize) {
    clear_table();
    let statement = CString::new(format!(
        "loop.run_until_complete(insert_pyro_async({iterations}))"
    ))
    .unwrap();
    Python::attach(|py| {
        py.eval(&statement, None, None).unwrap();
    });
    coz::progress!("insert-iteration");
}

fn main() {
    setup_db();

    Python::attach(|py| {
        Python::run(
            py,
            c_str!(include_str!("../benches/bench_pyro.py")),
            None,
            None,
        )
        .unwrap();
    });

    // repeat
    for _ in 0..100 {
        bench_insert_pyro_async(100);
    }

    for select_size in [1, 10, 100] {
        bench_select_pyro_sync(100, select_size);
        bench_select_pyro_async(100, select_size);
    }

    bench_insert_pyro_sync(100);
    bench_insert_pyro_async(100);
}
