#!/usr/bin/env bash
# Tests for bin/_human-session: the protected slice, the environment handover, and survival
# of the terminal that started the session.
#
# Exercised with a STUB runtime — no real agent runtime is started, and nothing this script
# did not create is ever signalled.
#
# Run: bash human-session.test.sh (exit 0 = all pass).
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LAUNCHER="${HUMAN_SESSION_LAUNCHER:-$ROOT/bin/_human-session}"
PASS=0; FAIL=0
ok()  { PASS=$((PASS+1)); printf 'PASS %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf 'FAIL %s\n     %s\n' "$1" "$2"; }

UID_NUM="$(id -u)"
HUMAN_CG="/user.slice/user-${UID_NUM}.slice/user@${UID_NUM}.service/human.slice"
SOCK="$HOME/.local/state/human-session/tmux.sock"

# Outside /tmp: the runtime runs under tmpjail, whose overlay would swallow these files.
TMP=$(mktemp -d "$HOME/.cache/human-session-test-XXXX")
STUB="$TMP/stub-runtime"
cat >"$STUB" <<'STUB'
#!/usr/bin/env bash
awk -F: '$1=="0"{print $3}' /proc/self/cgroup >"$HS_OUT/cgroup"
cat /proc/self/oom_score_adj >"$HS_OUT/oom"
printf '%s' "${HS_TOKEN:-MISSING}" >"$HS_OUT/token"
printf '%s' "${PYTHONPYCACHEPREFIX:-MISSING}" >"$HS_OUT/python-pycache-prefix"
printf '%s' "$*" >"$HS_OUT/argv"
sleep 300
STUB
chmod +x "$STUB"
mkdir -p "$TMP/bin" "$TMP/out"
cat >"$TMP/bin/node" <<'NODE'
#!/usr/bin/env bash
printf called >"$HS_OUT/node-called"
exit 99
NODE
chmod +x "$TMP/bin/node"

CLIENT_PID=""
SESSION=""
COLLISION_SESSION=""
cleanup() {
  [[ -n "$SESSION" ]] && tmux -S "$SOCK" kill-session -t "$SESSION" 2>/dev/null
  [[ -n "$COLLISION_SESSION" ]] && tmux -S "$SOCK" kill-session -t "$COLLISION_SESSION" 2>/dev/null
  [[ -n "$CLIENT_PID" ]] && kill "$CLIENT_PID" 2>/dev/null
  rm -rf "$TMP"
}
trap cleanup EXIT

# This test identifies its session by metadata; unrelated live sessions may start or stop concurrently.

LEDGER="$TMP/ledger"
LEDGER_ID="human-test-$$"
EXPECTED_REAP_ID="$(</proc/sys/kernel/random/uuid)"
mkdir -p "$LEDGER/sessions"
printf '{"schemaVersion":1,"ledgerId":"%s","runtime":"claude","cwd":"%s","startedAt":"2026-01-01T00:00:00Z","tmuxSession":"%s","tmuxSocket":"%s","tmuxReapIdentity":"%s"}\n' \
  "$LEDGER_ID" "$TMP" "$LEDGER_ID" "$SOCK" "$EXPECTED_REAP_ID" >"$LEDGER/sessions/$LEDGER_ID.json"

script -qec "cd $TMP && env -u TMPJAIL_ACTIVE PATH=$TMP/bin:\$PATH HS_OUT=$TMP/out HS_TOKEN=handover-ok PYTHONPYCACHEPREFIX=%h/.cache/python-bytecode AGENT_SESSIONS_DIR=$LEDGER AGENT_LEDGER_ID=$LEDGER_ID AGENT_LEDGER_HUMAN_TARGET=$LEDGER_ID AGENT_LEDGER_HUMAN_REAP_ID=$EXPECTED_REAP_ID $LAUNCHER $STUB --flag 'two words'" /dev/null \
  >"$TMP/client.log" 2>&1 &
CLIENT_PID=$!

for _ in $(seq 1 100); do
  [[ -s "$TMP/out/cgroup" ]] && break
  sleep 0.2
done

if [[ ! -s "$TMP/out/cgroup" ]]; then
  bad "session starts" "stub never ran; client log: $(head -c 400 "$TMP/client.log")"
  printf 'human-session: pass=%d fail=%d\n' "$PASS" "$FAIL"
  exit 1
fi
ok "session starts and the runtime runs"

CG="$(cat "$TMP/out/cgroup")"
if [[ "$CG" == "$HUMAN_CG"/* ]]; then
  ok "runtime runs inside human.slice ($CG)"
else
  bad "runtime runs inside human.slice" "cgroup=$CG"
fi

OOM="$(cat "$TMP/out/oom" 2>/dev/null)"
if [[ "$OOM" == "-900" ]]; then
  ok "runtime inherits oom_score_adj=-900"
else
  bad "runtime inherits oom_score_adj=-900" "got '$OOM' (root via deck-sudo is required to lower it)"
fi

if [[ "$(cat "$TMP/out/token" 2>/dev/null)" == "handover-ok" ]]; then
  ok "launching shell's environment reaches the runtime"
else
  bad "launching shell's environment reaches the runtime" "token='$(cat "$TMP/out/token" 2>/dev/null)'"
fi

if [[ "$(cat "$TMP/out/python-pycache-prefix" 2>/dev/null)" == "$HOME/.cache/python-bytecode" ]]; then
  ok "stale systemd home specifier is expanded before environment handover"
else
  bad "stale systemd home specifier is expanded before environment handover" \
    "prefix='$(cat "$TMP/out/python-pycache-prefix" 2>/dev/null)'"
fi

if [[ "$(cat "$TMP/out/argv" 2>/dev/null)" == "--flag two words" ]]; then
  ok "argv passthrough is exact"
else
  bad "argv passthrough is exact" "argv='$(cat "$TMP/out/argv" 2>/dev/null)'"
fi

STUB_PID=""

# Find only the session created by this test; the shared server can host attached user sessions.
for _ in $(seq 1 100); do
  SESSION="$(tmux -S "$SOCK" list-sessions \
    -F '#{session_name}|#{@human_cwd}|#{@human_runtime}' 2>/dev/null |
    awk -F '|' -v cwd="$TMP" '$2 == cwd && $3 == "stub-runtime" { print $1; exit }')"
  [[ -n "$SESSION" ]] && break
  sleep 0.2
done
if [[ -n "$SESSION" ]]; then
  STUB_PID="$(tmux -S "$SOCK" display-message -p -t "$SESSION" '#{pane_pid}')"
  ok "the test session exists ($SESSION)"
  REAP_ID="$(tmux -S "$SOCK" show-options -v -t "$SESSION" @agent_reap_identity 2>/dev/null || :)"
  if [[ "$REAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ ]]; then
    ok "the test session has a launch-time reap identity"
  else
    bad "the test session has a launch-time reap identity" "value='$REAP_ID'"
  fi
else
  bad "the test session exists" "$(tmux -S "$SOCK" list-sessions 2>&1 | head -3)"
fi

ENTRY="$LEDGER/sessions/$LEDGER_ID.json"
ledger_target_matches() {
  /usr/bin/python3 -c \
    'import json, sys; e=json.load(open(sys.argv[1])); raise SystemExit(e.get("tmuxSession") != sys.argv[2] or e.get("tmuxSocket") != sys.argv[3] or e.get("tmuxReapIdentity") != sys.argv[4])' \
    "$ENTRY" "$SESSION" "$SOCK" "$REAP_ID"
}
for _ in $(seq 1 100); do
  ledger_target_matches && break
  sleep 0.05
done
if ledger_target_matches; then
  ok "the ledger entry records the tmux target the session can be reopened through"
else
  bad "the ledger entry records the tmux target the session can be reopened through" "$(cat "$ENTRY")"
fi

if [[ ! -e "$TMP/out/node-called" ]]; then
  ok "a prebound ledger target needs no Node patch process"
else
  bad "a prebound ledger target needs no Node patch process" "node was invoked"
fi

# The entry carries no dtach mux, so this line can only come from the tmux target being probed live.
if timeout 60 env AGENT_SESSIONS_DIR="$LEDGER" "$ROOT/bin/agent-sessions" 2>&1 |
   grep -q "agent-sessions attach $LEDGER_ID"; then
  ok "a live tmux-hosted session is listed as reopenable"
else
  bad "a live tmux-hosted session is listed as reopenable" \
    "$(timeout 60 env AGENT_SESSIONS_DIR="$LEDGER" "$ROOT/bin/agent-sessions" 2>&1 | head -12)"
fi

# Without a terminal tmux itself refuses; what is under test is that agent-sessions routes to it
# at all instead of declaring a live tmux-hosted session dead.
if timeout 20 env AGENT_SESSIONS_DIR="$LEDGER" "$ROOT/bin/agent-sessions" attach "$LEDGER_ID" \
   </dev/null 2>&1 | grep -q "not running any more"; then
  bad "agent-sessions can reopen a tmux-hosted session" "attach refused a live tmux target"
else
  ok "agent-sessions can reopen a tmux-hosted session"
fi

matching="$(tmux -S "$SOCK" list-sessions \
  -F '#{@human_cwd}|#{@human_runtime}' 2>/dev/null |
  awk -F '|' -v cwd="$TMP" '$1 == cwd && $2 == "stub-runtime"' | wc -l)"
if (( matching == 1 )); then
  ok "exactly one test session exists"
else
  bad "exactly one test session exists" "matching=$matching"
fi

# Terminal death: kill the client the way a gnome-terminal-server crash would.
kill -9 "$CLIENT_PID" 2>/dev/null
wait "$CLIENT_PID" 2>/dev/null || :
CLIENT_PID=""
sleep 2

if [[ -n "$STUB_PID" ]] && kill -0 "$STUB_PID" 2>/dev/null; then
  ok "runtime survives the terminal being killed (pid $STUB_PID)"
else
  bad "runtime survives the terminal being killed" "stub pid '$STUB_PID' is gone"
fi

if tmux -S "$SOCK" list-sessions -F '#{session_name} #{session_attached}' 2>/dev/null |
   awk -v s="$SESSION" '$1 == s && $2 == 0 {found=1} END {exit !found}'; then
  ok "session is now detached and reattachable"
else
  bad "session is now detached and reattachable" "$(tmux -S "$SOCK" list-sessions 2>&1 | head -3)"
fi

if "$ROOT/bin/claude-sessions" list 2>&1 | grep -q "$SESSION"; then
  ok "claude-sessions lists the surviving session"
else
  bad "claude-sessions lists the surviving session" "$("$ROOT/bin/claude-sessions" list 2>&1 | head -5)"
fi

spawn_files="$(compgen -G "$HOME/.local/state/human-session/spawn.*" || :)"
if [[ -z "$spawn_files" ]]; then
  ok "spawn files carrying the environment are unlinked"
else
  bad "spawn files carrying the environment are unlinked" "$spawn_files"
fi

COLLISION_SESSION="human-collision-$$"
COLLISION_REAP_ID="$(</proc/sys/kernel/random/uuid)"
COLLISION_ORIGINAL_ID="$(</proc/sys/kernel/random/uuid)"
COLLISION_STUB="$TMP/collision-runtime"
cat >"$COLLISION_STUB" <<'STUB'
#!/usr/bin/env bash
printf ran >"$HS_OUT/collision-runtime-ran"
STUB
chmod +x "$COLLISION_STUB"
tmux -S "$SOCK" new-session -d -s "$COLLISION_SESSION" 'sleep 300'
tmux -S "$SOCK" set-option -t "$COLLISION_SESSION" @agent_reap_identity "$COLLISION_ORIGINAL_ID"
if timeout 30 env -u TMPJAIL_ACTIVE PATH="$TMP/bin:$PATH" HS_OUT="$TMP/out" \
  AGENT_LEDGER_ID="$COLLISION_SESSION" AGENT_LEDGER_HUMAN_TARGET="$COLLISION_SESSION" \
  AGENT_LEDGER_HUMAN_REAP_ID="$COLLISION_REAP_ID" "$LAUNCHER" "$COLLISION_STUB" \
  >"$TMP/collision-client.log" 2>&1; then
  collision_installed_id="$(tmux -S "$SOCK" show-options -v -t "$COLLISION_SESSION" \
    @agent_reap_identity 2>/dev/null || :)"
  if [[ "$collision_installed_id" == "$COLLISION_ORIGINAL_ID" && \
        -e "$TMP/out/collision-runtime-ran" ]]; then
    ok "a pre-existing prebound tmux target is never accepted or rebound"
  else
    bad "a pre-existing prebound tmux target is never accepted or rebound" \
      "token='$collision_installed_id' runtime-ran=$([[ -e "$TMP/out/collision-runtime-ran" ]] && printf yes || printf no)"
  fi
else
  bad "a pre-existing prebound tmux target is never accepted or rebound" \
    "$(head -c 400 "$TMP/collision-client.log")"
fi

printf 'human-session: pass=%d fail=%d\n' "$PASS" "$FAIL"
(( FAIL == 0 ))
