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

const STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA: &[&str] = &[
    "some_or_err_i32",
    "some_or_err_i64",
    "some_or_err_f64",
    "some_or_err_bool",
    "some_or_err_string",
];

#[test]
fn workspace_package_imports_standard_source_module() {
    let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let workspace = compiler_root.join("../examples/workspaces/std-import-option");
    let main = workspace.join("packages/app/src/main.slo");
    let slovo_option = compiler_root.join("../lib/std/option.slo");

    assert!(
        !workspace.join("packages/app/src/option.slo").exists(),
        "workspace std-import-option must not carry a local option module copy"
    );
    assert!(
        !workspace.join("packages/app/src/bridge.slo").exists(),
        "workspace std-import-option must not depend on a local bridge shim"
    );
    assert!(
        read(&main).starts_with("(module main)\n\n(import std.option ("),
        "workspace fixture must exercise explicit `std.option` import syntax"
    );
    assert!(
        read(&slovo_option).starts_with("(module option (export "),
        "repo-root Slovo std/option.slo must export imported helpers directly"
    );
    for helper in STANDARD_OPTION_RESULT_BRIDGE_HELPERS_ALPHA {
        assert!(
            read(&slovo_option).contains(&format!("(fn {} ", helper)),
            "repo-root Slovo std/option.slo is missing bridge helper `{}`",
            helper
        );
        assert!(
            read(&main).contains(helper),
            "workspace std-import-option must import/use bridge helper `{}`",
            helper
        );
    }

    let check = run_glagol([OsStr::new("check"), workspace.as_os_str()]);
    assert_success_stdout(check, "", "workspace std option check");

    let test = run_glagol([OsStr::new("test"), workspace.as_os_str()]);
    assert_success_stdout(
        test,
        "test \"workspace std option import\" ... ok\n1 test(s) passed\n",
        "workspace std option 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);
}
