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

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

    assert!(
        output.status.success(),
        "compiler rejected local variable fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define i32 @add_local(i32 %a)")
            && stdout.contains("%total.addr = alloca i32")
            && stdout.contains("add i32 %a, 1")
            && stdout.contains("store i32")
            && stdout.contains("load i32")
            && stdout.contains("define i1 @keep_flag(i1 %flag)")
            && !stdout.contains("%local_flag.addr = alloca i1")
            && stdout.contains("define i1 @flip_flag(i1 %flag)")
            && stdout.contains("%current.addr = alloca i1")
            && stdout.contains("%count.addr = alloca i64")
            && stdout.contains("%amount.addr = alloca double"),
        "LLVM output did not contain expected local variable value/storage shape\nstdout:\n{}",
        stdout
    );
    assert!(
        !stdout.contains("locals work")
            && !stdout.contains("bool let locals work")
            && !stdout.contains("bool let locals preserve false")
            && !stdout.contains("bool var set flips true")
            && !stdout.contains("bool var set flips false")
            && !stdout.contains("i64 var set works")
            && !stdout.contains("f64 var set works"),
        "compiler emitted test metadata into LLVM output\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
}

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

    assert!(
        output.status.success(),
        "test runner rejected local variable fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert_eq!(
        stdout,
        concat!(
            "test \"locals work\" ... ok\n",
            "test \"bool let locals work\" ... ok\n",
            "test \"bool let locals preserve false\" ... ok\n",
            "test \"bool var set flips true\" ... ok\n",
            "test \"bool var set flips false\" ... ok\n",
            "test \"i64 var set works\" ... ok\n",
            "test \"f64 var set works\" ... ok\n",
            "7 test(s) passed\n",
        ),
        "test runner output drifted"
    );
    assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr);
}

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

    assert!(
        output.status.success(),
        "formatter rejected local variable fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert_eq!(stdout, expected, "formatter output drifted");
    assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", 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")
}
