use std::{fs, path::Path, process::Command};

#[test]
fn while_fixture_emits_llvm_loop_shape() {
    let output = run_glagol(["../tests/while.slo"]);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "compiler rejected while fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define i32 @count_to(i32 %limit)")
            && stdout.contains("while.cond")
            && stdout.contains("while.body")
            && stdout.contains("while.end")
            && stdout.contains("br i1")
            && stdout.contains("br label %while.cond"),
        "LLVM output did not contain expected while loop shape\nstdout:\n{}",
        stdout
    );
    assert!(
        !stdout.contains("while counts"),
        "compiler emitted test metadata into LLVM output\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
}

#[test]
fn while_fixture_runs_top_level_tests() {
    let output = run_glagol(["--run-tests", "../tests/while.slo"]);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "test runner rejected while fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert_eq!(
        stdout,
        concat!(
            "test \"while counts\" ... ok\n",
            "test \"while false skips\" ... ok\n",
            "2 test(s) passed\n",
        ),
        "test runner output drifted"
    );
    assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr);
}

#[test]
fn while_fixture_is_formatter_stable() {
    let expected = fs::read_to_string("../tests/while.slo").expect("read fixture");
    let output = run_glagol(["--format", "../tests/while.slo"]);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "formatter rejected while fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert_eq!(stdout, expected, "formatter output drifted");
    assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr);
}

#[test]
fn while_fixture_prints_lowered_shape() {
    let surface = run_glagol(["--inspect-lowering=surface", "../tests/while.slo"]);
    let surface_stdout = String::from_utf8_lossy(&surface.stdout);
    let surface_stderr = String::from_utf8_lossy(&surface.stderr);
    assert!(
        surface.status.success(),
        "surface lowering rejected while fixture\nstdout:\n{}\nstderr:\n{}",
        surface_stdout,
        surface_stderr
    );
    assert!(
        surface_stdout.contains("while\n")
            && surface_stdout.contains("binary <")
            && surface_stdout.contains("set i"),
        "surface lowering output lost while shape\nstdout:\n{}",
        surface_stdout
    );
    assert!(
        surface_stderr.is_empty(),
        "surface lowering wrote stderr:\n{}",
        surface_stderr
    );

    let checked = run_glagol(["--inspect-lowering=checked", "../tests/while.slo"]);
    let checked_stdout = String::from_utf8_lossy(&checked.stdout);
    let checked_stderr = String::from_utf8_lossy(&checked.stderr);
    assert!(
        checked.status.success(),
        "checked lowering rejected while fixture\nstdout:\n{}\nstderr:\n{}",
        checked_stdout,
        checked_stderr
    );
    assert!(
        checked_stdout.contains("while : unit")
            && checked_stdout.contains("binary < : bool")
            && checked_stdout.contains("set i : unit"),
        "checked lowering output lost typed while shape\nstdout:\n{}",
        checked_stdout
    );
    assert!(
        checked_stderr.is_empty(),
        "checked lowering wrote stderr:\n{}",
        checked_stderr
    );
}

fn run_glagol<const N: usize>(args: [&str; N]) -> std::process::Output {
    Command::new(env!("CARGO_BIN_EXE_glagol"))
        .args(args)
        .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
        .output()
        .expect("run glagol")
}
