#!/usr/bin/env bash
# semurg-arena -- one command to reproduce the Semurg arena board on YOUR hardware.
# Open, public-license engines only on the board; license-restricted engines are a separate opt-in,
# local-only step. Equal-answer: every lane answers the SAME queries on the SAME deterministic data,
# and a lane counts only if its answer hash matches the reference. No fake numbers, ever.
set -uo pipefail
KIT="$(cd "$(dirname "$0")/.."&&pwd)"
LANES="$KIT/lanes"; WORK="$KIT/workload"; MAN="$KIT/MANIFEST.tsv"
# Board helpers (docker status/fix, container reaping, timing, answer hashing) live in _common.sh.
# Sourcing it is REQUIRED: without it `run --all`/`list` call undefined functions. (launch-crit fix)
[ -f "$LANES/_common.sh" ] && . "$LANES/_common.sh" || { echo "arena: missing lanes/_common.sh (broken checkout)" >&2; exit 2; }
# ARENA_DATA is what every lane reads (lanes do `DATA="${ARENA_DATA:?}"`). Set + EXPORT the var
# ITSELF (not a differently-named local), so a bare `semurg-arena run --all` -- with ARENA_DATA unset,
# the normal non-reader case -- exports a real path, not an empty string. Exporting an empty ARENA_DATA
# made every lane abort with "ARENA_DATA: parameter null or not set".
ARENA_DATA="${ARENA_DATA:-$KIT/.arena_data}"; export ARENA_DATA; DATA="$ARENA_DATA"
DRY=0

usage(){ cat <<U
semurg-arena -- reproducible arena board (measured on YOUR hardware)

  semurg-arena list                 list every lane + its licence + wired/planned status
  semurg-arena gen                  generate the deterministic dataset (SEMURG_ARENA_ROWS=N)
  semurg-arena run --all            run the Semurg board + all WIRED public-license lanes
  semurg-arena run --graph          the GRAPH head-to-head: Semurg vs Neo4j vs Kuzu, equal-answer
                                    k-hop, in-core AND out-of-core (the survive-vs-DNF crown)
  semurg-arena run --olap           the OLAP head-to-head: Semurg (scan + fold) vs DuckDB, equal-answer
                                    COUNT(*) GROUP BY (honest raw-scan loss + the O(1) fold win)
  semurg-arena run <lane>           run one lane (e.g. sqlite, duckdb, postgres)
  (the public suite ships NO license-restricted lanes; DeWitt engines are internal-only)
  semurg-arena install              install the bundled Semurg R11 release (native, on this box)
  semurg-arena --dry-run run --all  show what would run, run nothing
  semurg-arena --help

Public-license engines are the public board. License-restricted engines (kdb+, Elasticsearch,
TigerGraph, Memgraph) are NEVER on the public board and NEVER uploaded -- you run them locally
under your own licence via 'run --licensed'.
U
}

lane_rows(){ grep -vE '^#|^\s*$' "$MAN"; }
gen(){ mkdir -p "$DATA"; ( cd "$DATA" && bash "$WORK/gen_dataset.sh" orders.csv ); }

do_list(){
  printf "%-14s %-11s %-26s %-16s %s\n" LANE CATEGORY LICENCE ENGINE STATUS
  awk -F'\t' '!/^#/ && NF>=5 {printf "%-14s %-11s %-26s %-16s %s\n",$1,$2,$3,$4,$5}' "$MAN"
}

run_lane(){ # name  -- ISOLATED: a lane can never hang the board (timeout) and never abort it (|| report)
  local n="$1" script="$LANES/$1.sh"
  [ -f "$script" ] || script="$LANES/licensed/$1.sh"
  if [ ! -f "$script" ]; then echo "LANE=$n status=UNKNOWN"; return; fi
  if [ "$DRY" = 1 ]; then echo "[dry-run] would run $script"; return; fi
  local to="${LANE_TIMEOUT:-300}"
  if command -v timeout >/dev/null 2>&1; then
    timeout -k 10 "${to}s" bash "$script" || echo "LANE=$n status=FAILED reason=timed-out-or-errored(>${to}s; a stuck engine can never hang the board)"
  else
    bash "$script" || echo "LANE=$n status=FAILED reason=lane-errored"
  fi
}

run_all(){
  # ENTRY cleanup: reap any arena-labelled containers left by an interrupted prior run, so back-to-back
  # runs never wedge each other. EXIT/INT/TERM cleanup: leave nothing behind (Ctrl-C included).
  arena_cleanup_containers
  trap 'arena_cleanup_containers' EXIT INT TERM
  [ -f "$DATA/orders.csv" ] || gen
  echo "== Semurg arena board  (measured on THIS machine: $(uname -sm), $(nproc) cores) =="
  echo "== dataset: $DATA/orders.csv  rows=$(( $(wc -l < "$DATA/orders.csv") - 1 )) =="
  local ds; ds="$(arena_docker_status)"
  if [ "$ds" = ok ]; then echo "== docker: ready -> incumbent engine lanes will run =="
  else echo "== docker: $ds -> $(arena_docker_fix "$ds"). Docker lanes SKIP; embedded lanes (sqlite/duckdb) still run. =="; fi
  echo
  local results=() result_cats=()
  # public-license wired lanes, in manifest order
  while IFS=$'\t' read -r name cat lic eng status <&9; do
    case "$status" in
      wired|wired-docker)
        line="$(run_lane "$name")"; echo "$line"; results+=("$line"); result_cats+=("$cat");;
      wired-graph)
        # the graph lane has its OWN workload + parity gate (different from the SQL board); run it via
        # `semurg-arena run --graph`, not here, so a graph engine is never mis-scored on the SQL queries.
        : ;;
      wired-olap)
        # the OLAP head-to-head has its OWN workload; run it via `semurg-arena run --olap`.
        : ;;
      planned-category)
        echo "LANE=$name status=PLANNED reason=category-lane-not-in-kit-v1(defined-in-MANIFEST);honest-skip";;
      opt-in-local) : ;; # never on the public board
    esac
  done 9< <(lane_rows | awk -F'\t' '{print $1"\t"$2"\t"$3"\t"$4"\t"$5}')
  echo
  board_mismatch=0
  # EQUAL-ANSWER PARITY, PER CATEGORY. Lanes are equal-answer peers ONLY when they run the SAME
  # workload = the SAME category (relational/olap/kv/timeseries/document/stream/object/universal).
  # The first answering lane in a category is that category's reference; every other lane in the
  # category must match IT. Cross-category comparison is meaningless -- a KV point-read hash never
  # equals a relational-aggregate hash -- so a single global reference produced a FALSE PARITY FAIL
  # (exit 3 on any docker box). Grouping the gate on category fixes it without weakening it: a REAL
  # within-category disagreement still fails. A lane carries its answer hash as either ANSWER_HASH=<hex>
  # or ANSWER=<hex> (redis/clickhouse/kafka/stream/olap emit the ANSWER= key); both are the SAME 32-hex
  # hash_answer() digest, so accept either -- a lane that ran and answered is COUNTED, never mislabeled
  # SKIPPED just because it used the ANSWER= key.
  echo "== equal-answer parity gate (per-category; each lane matches its OWN category's reference) =="
  declare -A cat_ref
  local i r cat h n
  for i in "${!results[@]}"; do
    r="${results[$i]}"; cat="${result_cats[$i]}"
    h="$(sed -n 's/.*ANSWER_HASH=\([0-9a-f]*\).*/\1/p' <<<"$r")"
    [ -z "$h" ] && h="$(sed -n 's/.* ANSWER=\([0-9a-f]*\).*/\1/p' <<<"$r")"
    n="$(sed -n -e 's/^LANE=\([^ ]*\).*/\1/p' -e 's/^SKIP \([^ ]*\).*/\1/p' <<<"$r" | head -1)"; [ -z "$n" ] && n="?"
    if echo "$r" | grep -q 'status=FAILED'; then echo "  $n [$cat]: FAILED (engine error; not counted)"
    elif [ -z "$h" ]; then echo "  $n [$cat]: SKIPPED (engine not available / no answer emitted)"
    elif [ -z "${cat_ref[$cat]:-}" ]; then cat_ref[$cat]="$h"; echo "  $n [$cat]: EQUAL-ANSWER OK (category reference)"
    elif [ "$h" = "${cat_ref[$cat]}" ]; then echo "  $n [$cat]: EQUAL-ANSWER OK"
    else echo "  $n [$cat]: MISMATCH ($h != ${cat_ref[$cat]} within $cat) -- NOT counted"; board_mismatch=1; fi
  done
  echo
  [ "${board_mismatch:-0}" = 0 ] || { echo; echo "PARITY FAIL: a lane disagreed with ITS CATEGORY's equal-answer reference (MISMATCH above). Exiting non-zero."; arena_cleanup_containers; exit 3; }
  echo "Add the Semurg lane:  semurg-arena install   (then re-run). Your own license-restricted engines"
  echo "run LOCAL-ONLY, shown only to you:  I_HAVE_A_LICENCE=yes semurg-arena run --licensed"
  echo "GRAPH head-to-head (Semurg vs Neo4j vs Kuzu, in-core + out-of-core crown):  semurg-arena run --graph"
  echo "OLAP head-to-head  (Semurg scan+fold vs DuckDB, honest loss + fold win):    semurg-arena run --olap"
  arena_cleanup_containers
}

run_olap(){
  [ -f "$KIT/olap_run.sh" ] || { echo "olap lane not present in this kit (olap_run.sh missing)"; exit 2; }
  OLAP_LANES_DIR="$LANES" OLAP_EXS="$WORK/semurg_olap.exs" bash "$KIT/olap_run.sh"
}

run_graph(){
  # ISOLATED like the SQL board: entry+exit container cleanup so a stuck/timed-out engine never wedges
  # a back-to-back run. The graph orchestrator owns its own deterministic workload + equal-answer gate.
  arena_cleanup_containers
  trap 'arena_cleanup_containers' EXIT INT TERM
  [ -f "$KIT/graph_run.sh" ] || { echo "graph lane not present in this kit (graph_run.sh missing)"; exit 2; }
  GRAPH_LANES_DIR="$LANES" GRAPH_WORK_DIR="$WORK" GRAPH_WALK_EXS="$WORK/semurg_walk.exs" bash "$KIT/graph_run.sh"
  arena_cleanup_containers
}

install_semurg(){
  local t; t="$(mktemp -d)"
  if [ -f "$KIT/installer/semurg-r11-installer.tar.gz" ]; then
    # A full kit bundles the installer: checksum-verify it, then unpack.
    echo "Installing the bundled Semurg R11 release (native, on this box)..."
    ( cd "$KIT/installer" && sha256sum -c SHA256SUMS >/dev/null 2>&1 ) \
      || { echo "REFUSING to install: the bundled installer FAILED its checksum (corrupt/truncated kit)."; rm -rf "$t"; exit 2; }
    echo "  bundled installer checksum OK"
    tar xzf "$KIT/installer/semurg-r11-installer.tar.gz" -C "$t" \
      || { echo "could not unpack the bundled installer (corrupt tarball)"; rm -rf "$t"; exit 2; }
  else
    # The slim benchmark repo ships no engine: fetch the published installer from one.semurg.io, verify its
    # sha256 against the authoritative LATEST.json, then run it. This is the same artifact the Semurg-Install
    # repo uses, and the first node is always free.
    command -v curl >/dev/null 2>&1 || { echo "install needs curl (apt-get install -y curl)"; rm -rf "$t"; exit 2; }
    echo "Fetching the published Semurg R11 installer from one.semurg.io (checksum-verified)..."
    local lj url sha
    curl -fsSL "https://one.semurg.io/dl/LATEST.json" -o "$t/LATEST.json" 2>/dev/null || { echo "could not fetch the release manifest (LATEST.json)"; rm -rf "$t"; exit 2; }
    # verify the ed25519 release signature against a PINNED pubkey BEFORE trusting url/sha (verify-or-REFUSE)
    if ! command -v openssl >/dev/null 2>&1; then command -v apt-get >/dev/null 2>&1 && apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq openssl >/dev/null 2>&1 || true; fi
    command -v openssl >/dev/null 2>&1 || { echo "REFUSING: openssl is required to verify the release signature but could not be installed (apt-get install -y openssl), or install from https://github.com/One-Semurg/Semurg-Install"; rm -rf "$t"; exit 2; }
    printf '%s\n' '-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGmSXmU++yNyeIS3rHFjAH1ppyErRrkxcovD6ljt1B4w=
-----END PUBLIC KEY-----' > "$t/semurg_release_pub.pem"
    curl -fsSL "https://one.semurg.io/dl/LATEST.json.sig" -o "$t/LATEST.json.sig" 2>/dev/null || { echo "REFUSING: no release signature at the channel; cannot verify authenticity"; rm -rf "$t"; exit 2; }
    openssl pkeyutl -verify -pubin -inkey "$t/semurg_release_pub.pem" -rawin -in "$t/LATEST.json" -sigfile "$t/LATEST.json.sig" >/dev/null 2>&1 || { echo "REFUSING: release manifest signature INVALID -- the channel may be compromised"; rm -rf "$t"; exit 2; }
    lj="$(cat "$t/LATEST.json")"
    url="$(printf '%s' "$lj" | sed -n 's/.*"installer_url"[: ]*"\([^"]*\)".*/\1/p' | head -1)"
    sha="$(printf '%s' "$lj" | sed -n 's/.*"sha256"[: ]*"\([^"]*\)".*/\1/p' | head -1)"
    [ -n "$url" ] || url="https://one.semurg.io/dl/semurg-r11-installer.tar.gz"
    curl -fsSL "$url" -o "$t/inst.tar.gz" || { echo "installer download failed ($url) -- check your network, or install from https://github.com/One-Semurg/Semurg-Install"; rm -rf "$t"; exit 2; }
    [ -n "$sha" ] || { echo "REFUSING to install: LATEST.json carried no sha256 -- will not run an unverified binary. Install from https://github.com/One-Semurg/Semurg-Install instead."; rm -rf "$t"; exit 2; }
    echo "$sha  $t/inst.tar.gz" | sha256sum -c - >/dev/null 2>&1 \
      || { echo "installer checksum MISMATCH vs LATEST.json -- aborting (never install an unverified binary)"; rm -rf "$t"; exit 2; }
    echo "  installer checksum OK"
    tar xzf "$t/inst.tar.gz" -C "$t" || { echo "could not unpack the downloaded installer"; rm -rf "$t"; exit 2; }
  fi
  ( cd "$t"/semurg_installer && exec bash ./semurg-install.sh )
}

# arg parse
while [ $# -gt 0 ]; do case "$1" in
  --dry-run) DRY=1; shift;;
  --help|-h) usage; exit 0;;
  list) do_list; exit 0;;
  gen) gen; exit 0;;
  install) install_semurg; exit 0;;
  run) shift
       case "${1:-}" in
         --all) run_all; exit 0;;
         --graph) run_graph; exit 0;;
         --olap) run_olap; exit 0;;
         --licensed) shift; [ "${I_HAVE_A_LICENCE:-}" = yes ] || { echo "run --licensed requires I_HAVE_A_LICENCE=yes (you assert you hold the vendor licences; Semurg never benchmarks or publishes these DeWitt-restricted engines)" >&2; exit 2; }; for l in kdb elasticsearch tigergraph memgraph dragonfly; do run_lane "$l"; done; exit 0;;
         "" ) echo "run what? see --help"; exit 2;;
         * ) [ -f "$DATA/orders.csv" ] || gen; run_lane "$1"; exit 0;;
       esac;;
  *) usage; exit 2;;
esac; done
usage
