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

#[test]
fn enum_payload_direct_scalars_alpha_fixture_emits_direct_payload_aggregates_and_tests_pass() {
    let fixture =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/enum-payload-direct-scalars.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 direct scalar/string enum payload fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define { i32, i32 } @value(i32 %payload)")
            && stdout.contains("define { i32, i64 } @wide_value(i64 %payload)")
            && stdout.contains("define { i32, double } @ratio_value(double %payload)")
            && stdout.contains("define { i32, i1 } @flag_value(i1 %payload)")
            && stdout.contains("define { i32, ptr } @label_value(ptr %payload)")
            && stdout.contains("extractvalue { i32, ptr }")
            && stdout.contains("define ptr @record_label_text(")
            && stdout.contains("switch i32"),
        "LLVM output did not contain expected direct payload enum aggregate shapes\nstdout:\n{}",
        stdout
    );

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"i32 enum constructor equality\" ... ok\n",
            "test \"i32 enum equality compares payload\" ... ok\n",
            "test \"i32 enum payloadless equality\" ... ok\n",
            "test \"i32 enum local return call flow\" ... ok\n",
            "test \"i64 enum constructor equality\" ... ok\n",
            "test \"i64 enum local return call flow\" ... ok\n",
            "test \"f64 enum constructor equality\" ... ok\n",
            "test \"f64 enum equality compares payload\" ... ok\n",
            "test \"bool enum constructor equality\" ... ok\n",
            "test \"bool enum match value\" ... ok\n",
            "test \"string enum constructor equality\" ... ok\n",
            "test \"string enum equality compares payload\" ... ok\n",
            "test \"string enum local return call flow\" ... ok\n",
            "test \"struct field enum i32 flow\" ... ok\n",
            "test \"struct field enum i64 flow\" ... ok\n",
            "test \"struct field enum f64 flow\" ... ok\n",
            "test \"struct field enum bool flow\" ... ok\n",
            "test \"struct field enum string flow\" ... ok\n",
            "18 test(s) passed\n",
        ),
    );
}

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

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