use stopwatch::Stopwatch;
use tokio::io::AsyncWriteExt;
use tokio::process;

#[tokio::main]
async fn main() -> eyre::Result<()> {
    tracing_subscriber::fmt().pretty().init();

    let client = dagger_sdk::connect().await?;

    let (tx, mut rx) = tokio::sync::mpsc::channel(4);

    async_scoped::TokioScope::scope_and_block(|s| {
        let dclient = client.clone();
        let dtx = tx.clone();
        s.spawn(async move {
            let dclient = dclient.pipeline("build-image");
            tracing::info!("building 1. complete image");

            let sw = Stopwatch::start_new();
            let src = dclient
                .host()
                .directory_opts(
                    "bench_app",
                    dagger_sdk::HostDirectoryOptsBuilder::default()
                        .exclude(vec!["target/"])
                        .build()
                        .unwrap(),
                )
                .id()
                .await
                .unwrap();

            let _ = dclient
                .container()
                .from("rustlang/rust:nightly")
                .with_directory("/mnt/app", src)
                .with_workdir("/mnt/app")
                .with_exec(vec!["cargo", "build", "--release"])
                .with_exec(vec!["target/release/bench_app", "bench"])
                .exit_code()
                .await
                .unwrap();

            dtx.send(format!(
                "1. build-image: {}",
                sw.elapsed_ms() as f64 / 1000.0
            ))
            .await
            .unwrap();
        });

        let dclient = client.clone();
        let dtx = tx.clone();
        s.spawn(async move {
            let dclient = dclient.pipeline("pull-image");
            tracing::info!("building 2. pull image");
            let sw = Stopwatch::start_new();

            if let Err(e) = process::Command::new("docker")
                .args(&[
                    "image",
                    "rm",
                    "kasperhermansen/dagger-runtime-benchmark:latest",
                ])
                .output()
                .await
            {
                tracing::warn!(msg = e.to_string(), "failed to delete image");
            }

            let _ = dclient
                .container()
                .from("kasperhermansen/dagger-runtime-benchmark:latest")
                .with_exec(vec!["/usr/bin/bench_app", "bench"])
                .exit_code()
                .await
                .unwrap();
            dtx.send(format!(
                "2. pull-image: {}",
                sw.elapsed_ms() as f64 / 1000.0
            ))
            .await
            .unwrap();
        });

        let dclient = client.clone();
        let dtx = tx.clone();
        s.spawn(async move {
            let dclient = dclient.pipeline("run-wasm");
            tracing::info!("building 3. pull wasm");
            let sw = Stopwatch::start_new();

            let src = dclient
                .host()
                .directory(".")
                .file("dist/wasm/bench_app.wasm")
                .id()
                .await
                .unwrap();

            if let Err(e) = process::Command::new("docker")
                .args(&["image", "rm", "kasperhermansen/dagger-wasmer:latest"])
                .output()
                .await
            {
                tracing::warn!(msg = e.to_string(), "failed to delete image");
            }

            let _ = dclient
                .container()
                .from("kasperhermansen/dagger-wasmer:latest")
                .with_file("/usr/bin/bench_app.wasm", src)
                .with_exec(vec!["wasmer", "run", "/usr/bin/bench_app.wasm", "bench"])
                .exit_code()
                .await
                .unwrap();

            dtx.send(format!("3. run-wasm: {}", sw.elapsed_ms() as f64 / 1000.0))
                .await
                .unwrap();
        });

        let dclient = client.clone();
        let dtx = tx.clone();
        s.spawn(async move {
            let dclient = dclient.pipeline("run-binary");
            tracing::info!("building 4. run binary");
            let sw = Stopwatch::start_new();

            let src = dclient
                .host()
                .directory(".")
                .file("dist/binary/bench_app")
                .id()
                .await
                .unwrap();

            if let Err(e) = process::Command::new("docker")
                .args(&["image", "rm", "debian:bullseye-slim"])
                .output()
                .await
            {
                tracing::warn!(msg = e.to_string(), "failed to delete image");
            }

            let _ = dclient
                .container()
                .from("debian:bullseye-slim")
                .with_file("/usr/bin/bench_app", src)
                .with_exec(vec!["/usr/bin/bench_app", "bench"])
                .exit_code()
                .await
                .unwrap();

            dtx.send(format!(
                "4. run-binary: {}",
                sw.elapsed_ms() as f64 / 1000.0
            ))
            .await
            .unwrap();
        });
    });

    drop(tx);

    let mut file = tokio::fs::File::create("reports/timings.md").await?;
    file.write_all(b"# Dagger runtime benchmark results\n")
        .await?;

    let mut results = Vec::new();
    while let Some(res) = rx.recv().await {
        results.push(res);
    }
    results.sort();

    for res in results {
        file.write_all(format!("{}\n", res).as_bytes()).await?;
    }

    file.sync_all().await?;

    Ok(())
}
