use std::env;
use std::io::Write;
use std::path::Path;
use std::process::Command;

use crate::path::Dirs;
use crate::prepare::GitRepo;
use crate::rustc_info::get_file_name;
use crate::utils::{Compiler, spawn_and_wait};

static SIMPLE_RAYTRACER_REPO: GitRepo = GitRepo::github(
    "ebobby",
    "simple-raytracer",
    "804a7a21b9e673a482797aa289a18ed480e4d813",
    "ad6f59a2331a3f56",
    "<none>",
);

pub(crate) fn benchmark(dirs: &Dirs, compiler: &Compiler) {
    if std::process::Command::new("hyperfine").output().is_err() {
        eprintln!("Hyperfine not installed");
        eprintln!("Hint: Try `cargo install hyperfine` to install hyperfine");
        std::process::exit(1);
    }

    SIMPLE_RAYTRACER_REPO.fetch(dirs);
    SIMPLE_RAYTRACER_REPO.patch(dirs);

    let bench_runs = env::var("BENCH_RUNS").unwrap_or_else(|_| "10".to_string()).parse().unwrap();

    let mut gha_step_summary = if let Ok(file) = std::env::var("GITHUB_STEP_SUMMARY") {
        Some(std::fs::OpenOptions::new().append(true).open(file).unwrap())
    } else {
        None
    };

    eprintln!("[BENCH COMPILE] ebobby/simple-raytracer");
    let cargo_clif = &compiler.cargo;
    let rustc_clif = &compiler.rustc;
    let rustflags = &compiler.rustflags.join("\x1f");
    let manifest_path = SIMPLE_RAYTRACER_REPO.source_dir().to_path(dirs).join("Cargo.toml");
    let target_dir = dirs.build_dir.join("simple-raytracer_target");

    let raytracer_cg_llvm = dirs
        .build_dir
        .join(get_file_name(&compiler.rustc, "raytracer_cg_llvm", "bin"))
        .to_str()
        .unwrap()
        .to_owned();
    let raytracer_cg_clif = dirs
        .build_dir
        .join(get_file_name(&compiler.rustc, "raytracer_cg_clif", "bin"))
        .to_str()
        .unwrap()
        .to_owned();
    let raytracer_cg_clif_opt = dirs
        .build_dir
        .join(get_file_name(&compiler.rustc, "raytracer_cg_clif_opt", "bin"))
        .to_str()
        .unwrap()
        .to_owned();

    let clean_cmd = format!(
        "RUSTC=rustc cargo clean --manifest-path {manifest_path} --target-dir {target_dir}",
        manifest_path = manifest_path.display(),
        target_dir = target_dir.display(),
    );
    let llvm_build_cmd = format!(
        "RUSTC=rustc cargo build --manifest-path {manifest_path} --target-dir {target_dir} && (rm {raytracer_cg_llvm} || true) && ln {target_dir}/debug/main {raytracer_cg_llvm}",
        manifest_path = manifest_path.display(),
        target_dir = target_dir.display(),
    );
    let clif_build_cmd = format!(
        "RUSTC={rustc_clif} CARGO_ENCODED_RUSTFLAGS=\"{rustflags}\" {cargo_clif} build --manifest-path {manifest_path} --target-dir {target_dir} && (rm {raytracer_cg_clif} || true) && ln {target_dir}/debug/main {raytracer_cg_clif}",
        cargo_clif = cargo_clif.display(),
        rustc_clif = rustc_clif.display(),
        manifest_path = manifest_path.display(),
        target_dir = target_dir.display(),
    );
    let clif_build_opt_cmd = format!(
        "RUSTC={rustc_clif} CARGO_ENCODED_RUSTFLAGS=\"{rustflags}\" CARGO_BUILD_INCREMENTAL=true {cargo_clif} build --manifest-path {manifest_path} --target-dir {target_dir} --release && (rm {raytracer_cg_clif_opt} || true) && ln {target_dir}/release/main {raytracer_cg_clif_opt}",
        cargo_clif = cargo_clif.display(),
        rustc_clif = rustc_clif.display(),
        manifest_path = manifest_path.display(),
        target_dir = target_dir.display(),
    );

    let bench_compile_markdown = dirs.build_dir.join("bench_compile.md");

    let bench_compile = hyperfine_command(
        0,
        bench_runs,
        Some(&clean_cmd),
        &[
            ("cargo build", &llvm_build_cmd),
            ("cargo-clif build", &clif_build_cmd),
            ("cargo-clif build --release", &clif_build_opt_cmd),
        ],
        &bench_compile_markdown,
    );

    spawn_and_wait(bench_compile);

    if let Some(gha_step_summary) = gha_step_summary.as_mut() {
        gha_step_summary.write_all(b"## Compile ebobby/simple-raytracer\n\n").unwrap();
        gha_step_summary.write_all(&std::fs::read(bench_compile_markdown).unwrap()).unwrap();
        gha_step_summary.write_all(b"\n").unwrap();
    }

    eprintln!("[BENCH RUN] ebobby/simple-raytracer");

    let bench_run_markdown = dirs.build_dir.join("bench_run.md");

    let mut bench_run = hyperfine_command(
        0,
        bench_runs,
        None,
        &[
            ("build/raytracer_cg_llvm", &raytracer_cg_llvm),
            ("build/raytracer_cg_clif", &raytracer_cg_clif),
            ("build/raytracer_cg_clif_opt", &raytracer_cg_clif_opt),
        ],
        &bench_run_markdown,
    );
    bench_run.current_dir(&dirs.build_dir);
    spawn_and_wait(bench_run);

    if let Some(gha_step_summary) = gha_step_summary.as_mut() {
        gha_step_summary.write_all(b"## Run ebobby/simple-raytracer\n\n").unwrap();
        gha_step_summary.write_all(&std::fs::read(bench_run_markdown).unwrap()).unwrap();
        gha_step_summary.write_all(b"\n").unwrap();
    }
}

#[must_use]
fn hyperfine_command(
    warmup: u64,
    runs: u64,
    prepare: Option<&str>,
    cmds: &[(&str, &str)],
    markdown_export: &Path,
) -> Command {
    let mut bench = Command::new("hyperfine");

    bench.arg("--export-markdown").arg(markdown_export);

    if warmup != 0 {
        bench.arg("--warmup").arg(warmup.to_string());
    }

    if runs != 0 {
        bench.arg("--runs").arg(runs.to_string());
    }

    if let Some(prepare) = prepare {
        bench.arg("--prepare").arg(prepare);
    }

    for &(name, cmd) in cmds {
        if !name.is_empty() {
            bench.arg("-n").arg(name);
        }
        bench.arg(cmd);
    }

    bench
}
