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

const EXPECTED_STD_MATH_OUTPUT: &str = concat!(
    "test \"explicit std math import i32 helpers\" ... ok\n",
    "test \"explicit std math import i64 helpers\" ... ok\n",
    "test \"explicit std math import f64 helpers\" ... ok\n",
    "test \"explicit std math import all helpers\" ... ok\n",
    "4 test(s) passed\n",
);

#[test]
fn explicit_std_math_import_loads_repo_root_standard_source() {
    let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let project = compiler_root.join("../examples/projects/std-import-math");
    let slovo_math = compiler_root.join("../lib/std/math.slo");

    assert!(
        !project.join("src/math.slo").exists(),
        "std-import-math must not carry a local math module copy"
    );
    assert!(
        read(&project.join("src/main.slo")).starts_with("(module main)\n\n(import std.math ("),
        "std-import-math must exercise explicit `std.math` import syntax"
    );
    assert!(
        read(&slovo_math).starts_with("(module math (export "),
        "repo-root Slovo std/math.slo must export imported helpers directly"
    );

    let fmt = run_glagol([
        OsStr::new("fmt"),
        OsStr::new("--check"),
        project.as_os_str(),
    ]);
    assert_success("std import math fmt --check", &fmt);

    let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
    assert_success_stdout(check, "", "std import math check");

    let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
    assert_success_stdout(test, EXPECTED_STD_MATH_OUTPUT, "std import math test");
}

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 read(path: &Path) -> String {
    fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
}

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);
}
