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

#[test]
fn composite_struct_fields_alpha_fixture_emits_composite_fields_and_tests_pass() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/composite-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 composite struct field fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains(
            "define { ptr, ptr, ptr, ptr, ptr, { i1, i32 }, { i1, i64 }, { i1, double }, { i1, i1 }, { i1, ptr }, { i1, i32 }, { i1, i64, i32 }, { i1, double, i32 }, { i1, i1, i32 }, { i1, ptr, i32 }, { i32, i32 }, i32 } @make_record()"
        ) && stdout.contains("define i32 @pair_total(")
            && stdout.contains("extractvalue { i32, i32 }")
            && stdout.contains("extractvalue { i1, i64 }")
            && stdout.contains("extractvalue { i1, ptr, i32 }"),
        "LLVM output did not contain expected composite struct field 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 \"struct vec i32 field access\" ... ok\n",
            "test \"struct vec i64 field access\" ... ok\n",
            "test \"struct vec f64 field access\" ... ok\n",
            "test \"struct vec bool field access\" ... ok\n",
            "test \"struct vec string field access\" ... ok\n",
            "test \"struct option i32 field access\" ... ok\n",
            "test \"struct option i64 field access\" ... ok\n",
            "test \"struct option f64 field access\" ... ok\n",
            "test \"struct option bool field access\" ... ok\n",
            "test \"struct option string field access\" ... ok\n",
            "test \"struct result i32 field access\" ... ok\n",
            "test \"struct result i64 field access\" ... ok\n",
            "test \"struct result f64 field access\" ... ok\n",
            "test \"struct result bool field access\" ... ok\n",
            "test \"struct result string field access\" ... ok\n",
            "test \"nested struct field access\" ... ok\n",
            "test \"nested struct local param return call flow\" ... ok\n",
            "test \"direct enum struct field equality\" ... ok\n",
            "18 test(s) passed\n",
        ),
    );
}

#[test]
fn composite_struct_fields_alpha_formatter_and_lowering_are_visible() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/composite-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 composite 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/composite-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/composite-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);
}
