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

#[test]
fn concrete_type_alias_fixture_erases_before_llvm_and_runs_tests() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");

    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);
    assert!(
        compile.status.success(),
        "compiler rejected concrete type alias fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define i32 @bump(i32 %value)")
            && stdout.contains("define [3 x i32] @make_counts(i32 %base)")
            && stdout.contains("define i32 @pick_count([3 x i32] %values)")
            && stdout.contains("define { i32, ptr } @make_measure(i32 %amount)")
            && stdout.contains("define { i1, i32 } @maybe_amount(i32 %amount)")
            && stdout.contains("define { i1, i32 } @ok_amount(i32 %amount)")
            && stdout.contains("define ptr @empty_counts()")
            && stdout.contains("define { i32, i32 } @reading_value(i32 %amount)")
            && stdout.contains("define i32 @reading_score({ i32, i32 } %reading)")
            && stdout.contains("call ptr @__glagol_vec_i32_empty()"),
        "LLVM output did not show erased concrete alias shapes\nstdout:\n{}",
        stdout
    );
    assert!(
        !stdout.contains("Count")
            && !stdout.contains("Score")
            && !stdout.contains("MaybeCount")
            && !stdout.contains("ReadingAlias"),
        "LLVM output leaked source alias names\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"aliases erase through arrays and structs\" ... ok\n",
            "test \"aliases erase through option result vec and enum\" ... ok\n",
            "2 test(s) passed\n",
        ),
    );
}

#[test]
fn concrete_type_alias_fixture_formats_and_lowers_stably() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        &std::fs::read_to_string(&fixture).expect("read type alias fixture"),
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success_stdout(
        surface,
        &std::fs::read_to_string(
            Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.surface.lower"),
        )
        .expect("read type alias surface snapshot"),
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success_stdout(
        checked,
        &std::fs::read_to_string(
            Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.checked.lower"),
        )
        .expect("read type alias checked snapshot"),
    );
}

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

fn glagol_bin() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_glagol"))
}

fn assert_success_stdout(output: Output, expected: &str) {
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "command failed\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert_eq!(stdout, expected);
    assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr);
}
