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

#[test]
fn primitive_struct_fields_alpha_fixture_emits_primitive_fields_and_tests_pass() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/primitive-struct-fields.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 primitive struct field fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define { i32, i1, ptr } @make_record")
            && stdout.contains("insertvalue { i32, i1, ptr }")
            && stdout.contains("extractvalue { i32, i1, ptr }")
            && stdout.contains("call i1 @__glagol_string_eq")
            && stdout.contains("call i32 @string_len"),
        "LLVM output did not contain expected primitive-in-struct aggregate shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"primitive struct i32 field access\" ... ok\n",
            "test \"primitive struct bool field predicate\" ... ok\n",
            "test \"primitive struct bool field false branch\" ... ok\n",
            "test \"primitive struct string field equality\" ... ok\n",
            "test \"primitive struct string field length\" ... ok\n",
            "test \"primitive struct local param return call flow\" ... ok\n",
            "test \"primitive struct string field access return\" ... ok\n",
            "7 test(s) passed\n",
        ),
    );
}

#[test]
fn primitive_struct_fields_alpha_formatter_and_lowering_are_visible() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/primitive-struct-fields.slo");

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        &std::fs::read_to_string(&fixture).expect("read primitive struct field 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/primitive-struct-fields.surface.lower"),
        )
        .expect("read 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/primitive-struct-fields.checked.lower"),
        )
        .expect("read 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);
}
