#!/usr/bin/env bash

set -u

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT" || exit 2

# Force RC loading off for deterministic quad runs regardless of user shell env.
export FLOWLOG_RC_FILE="$ROOT/build/.flowlog_test_rc_disabled"
rm -f "$FLOWLOG_RC_FILE" >/dev/null 2>&1 || true

usage() {
  cat >&2 <<'EOF'
usage: tests/quad.sh <prolog> [prolog_args...] [-- <quad_files...>]

Runs each `tests/**/*_quad.pl` file in a fresh Prolog process, so a crash/hang in
one test file does not prevent later files from running.

If one or more `*_quad.pl` files are provided after `--`, only those files are
run.

Environment:
  QUAD_TIMEOUT   Per-file timeout in seconds (default: 30)
  QUAD_RESULTS_DIR  Directory for per-test logs for failing/crashing files
                  (default: results; set to empty to disable)
EOF
  exit 2
}

if [[ $# -lt 1 ]]; then
  usage
fi

PROLOG="$1"
shift
PROLOG_ARGS=()
SELECTED_FILES=()

while [[ $# -gt 0 ]]; do
  if [[ "$1" == "--" ]]; then
    shift
    SELECTED_FILES=("$@")
    break
  fi
  PROLOG_ARGS+=("$1")
  shift
done

LIB="$ROOT/lib/quad.pl"
if [[ ! -f "$LIB" ]]; then
  echo "quad: missing library: $LIB" >&2
  exit 2
fi

if [[ "$PROLOG" == */* ]]; then
  if [[ ! -x "$PROLOG" ]]; then
    echo "quad: prolog not executable: $PROLOG" >&2
    exit 2
  fi
else
  if ! command -v "$PROLOG" >/dev/null 2>&1; then
    echo "quad: prolog not found in PATH: $PROLOG" >&2
    exit 2
  fi
fi

QUAD_TIMEOUT="${QUAD_TIMEOUT:-30}"
QUAD_RESULTS_DIR="${QUAD_RESULTS_DIR-results}"

normalize_quad_file() {
  local path="$1"
  if [[ -f "$path" ]]; then
    printf '%s\n' "$path"
    return 0
  fi
  if [[ -f "tests/$path" ]]; then
    printf '%s\n' "tests/$path"
    return 0
  fi
  echo "quad: no such quad file: $path" >&2
  return 1
}

QUAD_FILES=()
if [[ ${#SELECTED_FILES[@]} -gt 0 ]]; then
  for selected in "${SELECTED_FILES[@]}"; do
    if ! normalized="$(normalize_quad_file "$selected")"; then
      exit 2
    fi
    QUAD_FILES+=("$normalized")
  done
else
  mapfile -t QUAD_FILES < <(find tests -type f -name '*_quad.pl' | LC_ALL=C sort)
  if [[ ${#QUAD_FILES[@]} -eq 0 ]]; then
    echo "quad: no quad files found under tests/ (expected *_quad.pl)" >&2
    exit 2
  fi
fi

prolog_base="$(basename "$PROLOG")"
results_root=""
if [[ -n "$QUAD_RESULTS_DIR" ]]; then
  results_root="${QUAD_RESULTS_DIR%/}"
  if [[ "$prolog_base" != "flowlog" ]]; then
    results_root="$results_root/$prolog_base"
  fi
fi

mktemp_file() {
  local tmp
  tmp="$(mktemp 2>/dev/null)" && { echo "$tmp"; return 0; }
  tmp="$(mktemp -t quad 2>/dev/null)" && { echo "$tmp"; return 0; }
  echo "quad: mktemp failed" >&2
  exit 2
}

timeout_prefix=()
if command -v timeout >/dev/null 2>&1; then
  timeout_prefix=(timeout "$QUAD_TIMEOUT")
fi

pass=0
fail=0
crash=0
timed_out=0
failed_files=()
crashed_files=()

for file in "${QUAD_FILES[@]}"; do
  tmp_out="$(mktemp_file)"

  log_path=""
  if [[ -n "$results_root" ]]; then
    relative_file="$file"
    if [[ "$relative_file" == "$ROOT/"* ]]; then
      relative_file="${relative_file#$ROOT/}"
    fi
    if [[ "$relative_file" == tests/* ]]; then
      relative_file="${relative_file#tests/}"
    fi
    log_path="$results_root/$relative_file.log"
    rm -f "$log_path"
  fi

  # Always print a marker so the runner can classify the outcome even if the
  # engine doesn't reflect goal failure in its process exit code.
  goal="$(printf "(catch(quad_test_file('%s'), Ball, (write('quad: exception: '), write(Ball), nl, fail)) -> write('__QUAD_RUNNER_OK__') ; write('__QUAD_RUNNER_FAIL__')), nl, halt." "$file")"

  cmd=("$PROLOG" "${PROLOG_ARGS[@]}")
  case "$prolog_base" in
    flowlog)
      cmd+=(-f -g "$goal" "$LIB")
      ;;
    scryer-prolog)
      # NOTE: `-f` is "fast startup"; the library is a positional FILE.
      cmd+=("$LIB" -g "$goal")
      ;;
    tpl)
      cmd+=(-f "$LIB" -g "$goal")
      ;;
    *)
      echo "quad: unsupported prolog binary (expected ./flowlog, scryer-prolog, or tpl): $PROLOG" >&2
      echo "quad: library loading/goals may not work correctly" >&2
      cmd+=(-f "$LIB" -g "$goal")
      ;;
  esac

  if [[ ${#timeout_prefix[@]} -gt 0 ]]; then
    "${timeout_prefix[@]}" "${cmd[@]}" >"$tmp_out" 2>&1
    status=$?
  else
    "${cmd[@]}" >"$tmp_out" 2>&1
    status=$?
  fi

  if grep -q "__QUAD_RUNNER_OK__" "$tmp_out"; then
    pass=$((pass + 1))
  elif grep -q "__QUAD_RUNNER_FAIL__" "$tmp_out"; then
    fail=$((fail + 1))
    failed_files+=("$file")
    if [[ -n "$log_path" ]]; then
      mkdir -p "$(dirname "$log_path")"
      {
        echo "quad: FAIL $file"
        grep -v "__QUAD_RUNNER_" "$tmp_out" || true
      } >"$log_path"
      echo "quad: FAIL $file (log: $log_path)"
      sed '1d' "$log_path" || true
    else
      echo "quad: FAIL $file"
      grep -v "__QUAD_RUNNER_" "$tmp_out" || true
    fi
  else
    crash=$((crash + 1))
    crashed_files+=("$file")
    if [[ $status -eq 124 || $status -eq 137 ]]; then
      timed_out=$((timed_out + 1))
      crash_header="quad: CRASH $file (timeout QUAD_TIMEOUT=$QUAD_TIMEOUT)"
    else
      crash_header="quad: CRASH $file (exit=$status)"
    fi
    if [[ -n "$log_path" ]]; then
      mkdir -p "$(dirname "$log_path")"
      {
        echo "$crash_header"
        grep -v "__QUAD_RUNNER_" "$tmp_out" || true
      } >"$log_path"
      echo "$crash_header (log: $log_path)"
      sed '1d' "$log_path" || true
    else
      echo "$crash_header"
      grep -v "__QUAD_RUNNER_" "$tmp_out" || true
    fi
  fi

  rm -f "$tmp_out"
done

total=${#QUAD_FILES[@]}
if [[ $fail -eq 0 && $crash -eq 0 ]]; then
  echo "quad: all $total quad files passed"
  exit 0
fi
if [[ $fail -gt 0 ]]; then
  echo "quad: failing files:"
  printf '  %s\n' "${failed_files[@]}"
fi
if [[ $crash -gt 0 ]]; then
  if [[ $timed_out -gt 0 ]]; then
    echo "quad: crashed files (timeouts: $timed_out, QUAD_TIMEOUT=$QUAD_TIMEOUT):"
  else
    echo "quad: crashed files:"
  fi
  printf '  %s\n' "${crashed_files[@]}"
fi
if [[ -n "$results_root" ]]; then
  echo "quad: summary: total=$total pass=$pass fail=$fail crash=$crash logs=$results_root/"
else
  echo "quad: summary: total=$total pass=$pass fail=$fail crash=$crash"
fi

if [[ $fail -gt 0 || $crash -gt 0 ]]; then
  exit 1
fi
