use std::{
    fs,
    path::PathBuf,
    process::{Command, Output},
    sync::atomic::{AtomicUsize, Ordering},
};

static NEXT_ID: AtomicUsize = AtomicUsize::new(0);

#[test]
fn beta_scaffolded_project_runs_current_v1_7_gate_workflow() {
    let project = unique_path("beta-project");

    let scaffold = run_glagol(["new".as_ref(), project.as_os_str()]);
    assert_success("beta scaffold", &scaffold);

    fs::write(
        project.join("src/main.slo"),
        "(module main)\n\n(fn main () -> i32\n  (let values (array i32 3) (array i32 2 3 4))\n  (std.io.print_i32 (index values 1))\n  0)\n\n(test \"fixed array collection subset\"\n  (= (index (array i32 1 2 3) 2) 3))\n",
    )
    .expect("write beta project main");

    let fmt_check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]);
    assert_success("beta project fmt --check", &fmt_check);

    let test = run_glagol(["test".as_ref(), project.as_os_str()]);
    assert_success("beta project test", &test);
    assert_eq!(
        String::from_utf8_lossy(&test.stdout),
        "test \"fixed array collection subset\" ... ok\n1 test(s) passed\n"
    );

    let docs = unique_path("beta-project-docs");
    let doc = run_glagol([
        "doc".as_ref(),
        project.as_os_str(),
        "-o".as_ref(),
        docs.as_os_str(),
    ]);
    assert_success("beta project doc", &doc);
    let doc_index = fs::read_to_string(docs.join("index.md")).expect("read beta docs");
    assert!(doc_index.contains("# Project glagol-beta-"));
    assert!(doc_index.contains("- `main() -> i32`"));
    assert!(doc_index.contains("- `fixed array collection subset`"));

    let binary = unique_path("beta-project-bin");
    let build = run_glagol([
        "build".as_ref(),
        project.as_os_str(),
        "-o".as_ref(),
        binary.as_os_str(),
    ]);
    if build.status.success() {
        let run = Command::new(&binary).output().expect("run beta project");
        assert_success("beta project binary", &run);
        assert_eq!(String::from_utf8_lossy(&run.stdout), "3\n");
    } else {
        assert_stderr_contains("beta project build", &build, "ToolchainUnavailable");
    }
}

fn unique_path(name: &str) -> PathBuf {
    let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("system clock before UNIX_EPOCH")
        .as_nanos();
    std::env::temp_dir().join(format!(
        "glagol-beta-{}-{}-{}-{}",
        std::process::id(),
        nanos,
        id,
        name
    ))
}

fn run_glagol<I, S>(args: I) -> Output
where
    I: IntoIterator<Item = S>,
    S: AsRef<std::ffi::OsStr>,
{
    Command::new(env!("CARGO_BIN_EXE_glagol"))
        .args(args)
        .output()
        .expect("run glagol")
}

fn assert_success(context: &str, output: &Output) {
    assert!(
        output.status.success(),
        "{} failed\nstdout:\n{}\nstderr:\n{}",
        context,
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

fn assert_stderr_contains(context: &str, output: &Output, needle: &str) {
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains(needle),
        "{} stderr did not contain `{}`:\n{}",
        context,
        needle,
        stderr
    );
}
