use std::{
    ffi::OsStr,
    path::Path,
    process::{Command, Output},
};

const EXPECTED_OUTPUT: &str = concat!(
    "test \"result f64 ok constructor and match\" ... ok\n",
    "test \"result f64 err constructor and match\" ... ok\n",
    "test \"result bool ok constructor and match\" ... ok\n",
    "test \"result bool err constructor and match\" ... ok\n",
    "4 test(s) passed\n",
);

#[test]
fn result_f64_bool_constructors_and_match_are_source_supported() {
    let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let fixture = compiler_root.join("../examples/result-f64-bool-match.slo");

    let fmt = run_glagol([
        OsStr::new("fmt"),
        OsStr::new("--check"),
        fixture.as_os_str(),
    ]);
    assert_success("result f64 bool match fmt --check", &fmt);

    let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]);
    assert_success_stdout(check, "", "result f64 bool match check");

    let test = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
    assert_success_stdout(test, EXPECTED_OUTPUT, "result f64 bool match test");

    let llvm = run_glagol([OsStr::new("--emit=llvm"), fixture.as_os_str()]);
    assert_success("result f64 bool match llvm", &llvm);
    let stdout = String::from_utf8_lossy(&llvm.stdout);
    assert!(
        stdout.contains("insertvalue { i1, double, i32 }")
            && stdout.contains("insertvalue { i1, i1, i32 }")
            && stdout.contains("extractvalue { i1, double, i32 }")
            && stdout.contains("extractvalue { i1, i1, i32 }"),
        "lowering must use concrete f64/bool result aggregate shapes\n{}",
        stdout
    );
}

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)
        .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
        .output()
        .expect("run glagol")
}

fn assert_success(context: &str, output: &Output) {
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
        context,
        output.status.code(),
        stdout,
        stderr
    );
}

fn assert_success_stdout(output: Output, expected: &str, context: &str) {
    assert_success(context, &output);
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(stdout, expected, "{}", context);
}
