use anyhow::Result;

use crate::{Benchmark, bail_on_err};

pub fn run(args: &Benchmark) -> Result<()> {
    if args.wasm {
        unsafe { std::env::set_var("TREE_SITTER_BENCHMARK_WASM", "1") };
    }

    if let Some(ref example) = args.example_file_name {
        unsafe { std::env::set_var("TREE_SITTER_BENCHMARK_EXAMPLE_FILTER", example) };
    }

    if let Some(ref language) = args.language {
        unsafe { std::env::set_var("TREE_SITTER_BENCHMARK_LANGUAGE_FILTER", language) };
    }

    if args.repetition_count != 5 {
        unsafe {
            std::env::set_var(
                "TREE_SITTER_BENCHMARK_REPETITION_COUNT",
                args.repetition_count.to_string(),
            );
        };
    }

    if args.debug {
        let output = std::process::Command::new("cargo")
            .arg("bench")
            .arg("benchmark")
            .arg("-p")
            .arg("tree-sitter-cli")
            .args(
                args.wasm
                    .then_some(["--features", "wasm"])
                    .into_iter()
                    .flatten(),
            )
            .arg("--no-run")
            .arg("--message-format=json")
            .spawn()?
            .wait_with_output()?;

        bail_on_err(&output, "Failed to run `cargo bench`")?;

        let json_output = serde_json::from_slice::<serde_json::Value>(&output.stdout)?;

        let test_binary = json_output
            .as_array()
            .ok_or_else(|| anyhow::anyhow!("Invalid JSON output"))?
            .iter()
            .find_map(|message| {
                if message
                    .get("target")
                    .and_then(|target| target.get("name"))
                    .and_then(|name| name.as_str())
                    .is_some_and(|name| name == "benchmark")
                    && message
                        .get("executable")
                        .and_then(|executable| executable.as_str())
                        .is_some()
                {
                    message
                        .get("executable")
                        .and_then(|executable| executable.as_str())
                } else {
                    None
                }
            })
            .ok_or_else(|| anyhow::anyhow!("Failed to find benchmark executable"))?;

        println!("{test_binary}");
    } else {
        let status = std::process::Command::new("cargo")
            .arg("bench")
            .arg("benchmark")
            .arg("-p")
            .arg("tree-sitter-cli")
            .args(
                args.wasm
                    .then_some(["--features", "wasm"])
                    .into_iter()
                    .flatten(),
            )
            .status()?;

        if !status.success() {
            anyhow::bail!("Failed to run `cargo bench`");
        }
    }

    Ok(())
}
