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

#[test]
fn enum_payload_structs_alpha_fixture_emits_struct_payload_aggregates_and_tests_pass() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-payload-structs.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 struct enum payload fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define { [3 x i32], [2 x ptr], [2 x i1] } @make_packet(i32 %base, ptr %head)")
            && stdout.contains("define { i32, { [3 x i32], [2 x ptr], [2 x i1] } } @live({ [3 x i32], [2 x ptr], [2 x i1] } %packet)")
            && stdout.contains("define { i32, { [3 x i32], [2 x ptr], [2 x i1] } } @cached({ [3 x i32], [2 x ptr], [2 x i1] } %packet)")
            && stdout.contains("define ptr @state_label({ i32, { [3 x i32], [2 x ptr], [2 x i1] } } %state, i32 %i)")
            && stdout.contains("define i1 @state_flag({ i32, { [3 x i32], [2 x ptr], [2 x i1] } } %state, i32 %i)")
            && stdout.contains("extractvalue { i32, { [3 x i32], [2 x ptr], [2 x i1] } }")
            && stdout.contains("extractvalue { [3 x i32], [2 x ptr], [2 x i1] }")
            && stdout.contains("switch i32")
            && stdout.contains("call void @__glagol_array_bounds_trap()"),
        "LLVM output did not contain expected struct-payload enum aggregate shape\nstdout:\n{}",
        stdout
    );

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"struct payload missing arm\" ... ok\n",
            "test \"struct payload live constructor flow\" ... ok\n",
            "test \"struct payload cached param return call flow\" ... ok\n",
            "test \"struct payload string array field access\" ... ok\n",
            "test \"struct payload bool array field access\" ... ok\n",
            "test \"struct payload local return flow\" ... ok\n",
            "6 test(s) passed\n",
        ),
    );
    assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr);
}

#[test]
fn enum_payload_structs_alpha_formatter_and_lowering_are_visible() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/enum-payload-structs.slo");

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