#!/bin/bash
# m3te logic-test gate. Mirrors sx/tests/run_examples.sh: run each sx logic
# test, diff its stdout + exit code against committed expectations. Exits 0 iff
# ALL tests pass, NON-ZERO otherwise (so a broken assertion fails the build gate).
#
# Usage: bash tools/run_tests.sh [--update]
#   --update: regenerate the expected .stdout/.exit from current output.
#
# Layout (a test is any tests/<name>.sx with a tests/expected/<name>.exit marker;
# helper modules such as tests/test.sx have no marker, so they are not run):
#   tests/<name>.sx
#   tests/expected/<name>.stdout   # normalized stdout
#   tests/expected/<name>.exit     # process exit code
#
# SX binary: override with the SX env var; defaults to the dev build.

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
SX="${SX:-/Users/agra/projects/sx/zig-out/bin/sx}"
TESTS_DIR="$ROOT_DIR/tests"
EXPECTED_DIR="$TESTS_DIR/expected"
TIMEOUT=30

PASS=0
FAIL=0
SKIP=0
TIMEOUT_COUNT=0
UPDATE=0

if [[ "${1:-}" == "--update" ]]; then
    UPDATE=1
fi

if [[ ! -x "$SX" ]]; then
    echo "sx binary not found/executable at: $SX (override with SX=/path/to/sx)" >&2
    exit 2
fi

# Collapse the absolute test-file path back to its repo-relative form so the
# `FAIL <file>:<line>` lines printed by tests/test.sx are checkout-location
# independent. Applied identically to expected and actual, so it can only
# reconcile location noise, never desync an otherwise-matching pair.
normalize() {
    sed -E -e "s#${ROOT_DIR}/##g"
}

TMP_ERR="$(mktemp)"
trap 'rm -f "$TMP_ERR"' EXIT

shopt -s nullglob
markers=("$EXPECTED_DIR"/*.exit)
if [[ ${#markers[@]} -eq 0 ]]; then
    echo "no tests found under $EXPECTED_DIR (expected <name>.exit markers)" >&2
    exit 2
fi

for exit_file in "${markers[@]}"; do
    name=$(basename "$exit_file" .exit)
    sx_file="$TESTS_DIR/${name}.sx"
    out_file="$EXPECTED_DIR/${name}.stdout"

    if [[ ! -f "$sx_file" ]]; then
        printf "  %-40s SKIP (missing %s)\n" "$name" "tests/${name}.sx"
        SKIP=$((SKIP + 1))
        continue
    fi

    printf "  %-40s" "$name"
    actual_out=$(timeout "$TIMEOUT" "$SX" run "$sx_file" 2>"$TMP_ERR" | normalize)
    actual_exit=${PIPESTATUS[0]}

    if [[ $actual_exit -eq 124 ]]; then
        TIMEOUT_COUNT=$((TIMEOUT_COUNT + 1))
        echo "TIMEOUT (>${TIMEOUT}s)"
        continue
    fi

    if [[ $UPDATE -eq 1 ]]; then
        printf '%s\n' "$actual_out" > "$out_file"
        printf '%s\n' "$actual_exit" > "$exit_file"
        echo "updated (exit=$actual_exit)"
        continue
    fi

    expected_out=$(normalize < "$out_file" 2>/dev/null)
    expected_exit=$(cat "$exit_file")

    out_ok=true; exit_ok=true
    [[ "$actual_out" == "$expected_out" ]] || out_ok=false
    [[ "$actual_exit" == "$expected_exit" ]] || exit_ok=false

    if $out_ok && $exit_ok; then
        PASS=$((PASS + 1))
        echo "ok"
    else
        FAIL=$((FAIL + 1))
        echo "FAIL"
        $out_ok || { echo "  --- stdout diff (expected vs actual) ---"; diff <(printf '%s\n' "$expected_out") <(printf '%s\n' "$actual_out") || true; }
        $exit_ok || echo "  exit code: expected=$expected_exit actual=$actual_exit"
        [[ -s "$TMP_ERR" ]] && { echo "  --- stderr ---"; cat "$TMP_ERR"; }
    fi
done

if [[ $UPDATE -eq 1 ]]; then
    echo "Updated all expected output files."
    exit 0
fi

echo "$PASS passed, $FAIL failed, $SKIP skipped, $TIMEOUT_COUNT timed out"
[[ $FAIL -eq 0 && $TIMEOUT_COUNT -eq 0 ]]
