#!/usr/bin/env bash
set -euo pipefail

slot="$HOME/.claude/lib/buildslot.sh"
guard="$HOME/.claude/lib/cpu-guard.sh"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

fail() {
  printf 'FAIL: %s\n' "$*" >&2
  exit 1
}

run_counted() {
  "$slot" bash -c '
    exec 9>"$COUNTER_LOCK"
    flock 9
    current=$(<"$COUNTER")
    current=$((current + 1))
    printf "%s\n" "$current" >"$COUNTER"
    maximum=$(<"$MAXIMUM")
    if (( current > maximum )); then printf "%s\n" "$current" >"$MAXIMUM"; fi
    flock -u 9
    sleep "$SLEEP_SECS"
    flock 9
    current=$(<"$COUNTER")
    printf "%s\n" "$((current - 1))" >"$COUNTER"
  '
}

# Fixed mode for the mechanics tests — adaptation is tested separately below.
export BUILD_SLOT_FIXED=1
export BUILD_SLOT_DIR="$tmp/slots" BUILD_SLOTS=2
export COUNTER="$tmp/counter" MAXIMUM="$tmp/maximum" COUNTER_LOCK="$tmp/counter.lock" SLEEP_SECS=1
printf '0\n' >"$COUNTER"
printf '0\n' >"$MAXIMUM"

for _ in 1 2 3 4; do run_counted & done
wait
[[ $(<"$MAXIMUM") == 2 ]] || fail "expected at most 2 concurrent jobs, got $(<"$MAXIMUM")"
printf 'PASS: concurrency capped at 2\n'

BUILD_SLOT_SCRIPT="$slot" BUILD_SLOTS=1 "$slot" bash -c '"$BUILD_SLOT_SCRIPT" bash -c "exit 0"' || fail 'nested invocation blocked or failed'
printf 'PASS: reentrancy\n'

# env-scrubbed descendant (turbo passthrough shape): BUILD_SLOT_HELD stripped,
# ancestor holds the only slot -> must inherit via ancestor walk, not deadlock
BUILD_SLOT_SCRIPT="$slot" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=5 "$slot" bash -c \
  'env -u BUILD_SLOT_HELD "$BUILD_SLOT_SCRIPT" bash -c "exit 0"' \
  || fail 'env-scrubbed nested invocation deadlocked or failed'
printf 'PASS: reentrancy survives env scrubbing (ancestor walk)\n'

crash_dir="$tmp/crash-slots"
BUILD_SLOT_DIR="$crash_dir" BUILD_SLOTS=1 "$slot" sleep 30 &
holder=$!
sleep 0.2
ACQUIRED="$tmp/acquired" BUILD_SLOT_DIR="$crash_dir" BUILD_SLOTS=1 "$slot" bash -c 'printf acquired >"$ACQUIRED"' &
waiter=$!
sleep 0.2
kill -9 "$holder" 2>/dev/null || true
wait "$holder" 2>/dev/null || true
for _ in {1..30}; do
  [[ -f "$tmp/acquired" ]] && break
  sleep 0.1
done
wait "$waiter" || fail 'waiter failed after holder crash'
[[ -f "$tmp/acquired" ]] || fail 'slot was not released after holder crash'
printf 'PASS: crash release\n'

timeout_dir="$tmp/timeout-slots"
BUILD_SLOT_DIR="$timeout_dir" BUILD_SLOTS=1 "$slot" sleep 5 &
timeout_holder=$!
sleep 0.2
set +e
timeout_output="$(BUILD_SLOT_DIR="$timeout_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 "$slot" true 2>&1)"
timeout_status=$?
set -e
kill -9 "$timeout_holder" 2>/dev/null || true
wait "$timeout_holder" 2>/dev/null || true
(( timeout_status != 0 )) || fail 'timeout unexpectedly succeeded'
[[ "$timeout_output" == *'[buildslot] timed out waiting for build slot'* ]] || fail "missing timeout message: $timeout_output"
printf 'PASS: timeout\n'

bad_dir="$tmp/unwritable"
mkdir "$bad_dir"
chmod 500 "$bad_dir"
set +e
closed_output="$(SHOULD_NOT_RUN="$tmp/ran" BUILD_SLOT_DIR="$bad_dir" "$slot" bash -c 'touch "$SHOULD_NOT_RUN"' 2>&1)"
closed_status=$?
set -e
(( closed_status != 0 )) || fail 'unwritable lock directory unexpectedly succeeded'
[[ "$closed_output" == *'[buildslot] unable to use lock directory'* ]] || fail "missing fail-closed message: $closed_output"
[[ ! -e "$tmp/ran" ]] || fail 'command ran after lock-directory failure'
printf 'PASS: fail-closed lock directory\n'

# ---- FIFO fairness: earlier waiter always beats later arrivals ----
fifo_dir="$tmp/fifo-slots"
order="$tmp/order"
BUILD_SLOT_DIR="$fifo_dir" BUILD_SLOTS=1 "$slot" sleep 2 &
fifo_holder=$!
sleep 0.3
ORDER="$order" BUILD_SLOT_DIR="$fifo_dir" BUILD_SLOTS=1 "$slot" bash -c 'echo first >>"$ORDER"' &
first_waiter=$!
sleep 0.3
# burst of later arrivals that would win a pure slot race
for i in 2 3 4; do
  ORDER="$order" N="$i" BUILD_SLOT_DIR="$fifo_dir" BUILD_SLOTS=1 "$slot" bash -c 'echo "later-$N" >>"$ORDER"' &
done
wait "$fifo_holder" "$first_waiter"
wait
[[ "$(head -1 "$order")" == first ]] || fail "FIFO violated; admission order: $(tr '\n' ' ' <"$order")"
printf 'PASS: FIFO — oldest waiter admitted first despite later arrivals\n'

# A PID can remain live in another namespace (or be reused) after its waiter is
# gone. Such a legacy ticket must not block admission when the slot is free.
stale_dir="$tmp/stale-live-pid-slots"
mkdir -p "$stale_dir"
printf '%s\n' "$$" >"$stale_dir/queue"
BUILD_SLOT_DIR="$stale_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 "$slot" true \
  || fail 'stale ticket referencing a live unrelated PID blocked admission'
printf 'PASS: stale live-PID ticket pruned by ownership\n'

# A ticket lock can outlive its waiter when inherited by an unrelated descendant.
# A held ticket with no slot holder must not wedge all later admission forever.
stranded_dir="$tmp/stranded-ticket-slots"
mkdir -p "$stranded_dir"
: >"$stranded_dir/ticket.strand.lock"
printf 'ticket.strand.lock\n' >"$stranded_dir/queue"
exec {stranded_fd}>"$stranded_dir/ticket.strand.lock"
flock -n "$stranded_fd" || fail 'could not arrange stranded ticket lock'
BUILD_SLOT_DIR="$stranded_dir" BUILD_SLOTS=1 BUILD_SLOT_FIXED=1 BUILD_SLOT_TIMEOUT=2 "$slot" true \
  || fail 'stranded ticket lock blocked admission despite every slot being free'
exec {stranded_fd}>&-
printf 'PASS: stranded ticket cannot wedge free slots\n'

# Production queue format is one ticket basename per line in
# $BUILD_SLOT_DIR/queue. If the queue file is truncated while a waiter still
# owns its ticket, that waiter must restore its record instead of waiting
# forever with an orphaned ownership lock.
repair_dir="$tmp/repair-empty-queue-slots"
BUILD_SLOT_DIR="$repair_dir" BUILD_SLOTS=1 "$slot" sleep 2 &
repair_holder=$!
sleep 0.2
REPAIRED="$tmp/repaired" BUILD_SLOT_DIR="$repair_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=4 \
  "$slot" bash -c 'touch "$REPAIRED"' &
repair_waiter=$!
for _ in {1..20}; do
  [[ -s "$repair_dir/queue" ]] && break
  sleep 0.1
done
flock "$repair_dir/queue.lock" bash -c ': >"$1/queue"' _ "$repair_dir"
wait "$repair_holder"
wait "$repair_waiter" || fail 'waiter did not restore its missing basename queue record'
[[ -e "$tmp/repaired" ]] || fail 'repaired waiter never acquired the free slot'
printf 'PASS: waiter restores missing basename queue record\n'

# Orphan cleanup may remove only ownership files whose locks are acquirable.
orphan_dir="$tmp/orphan-ticket-slots"
mkdir -p "$orphan_dir"
: >"$orphan_dir/ticket.stale1.lock"
exec {held_orphan_fd}>"$orphan_dir/ticket.held01.lock"
flock -n "$held_orphan_fd" || fail 'could not arrange held orphan ticket'
BUILD_SLOT_DIR="$orphan_dir" BUILD_SLOTS=1 "$slot" true
[[ ! -e "$orphan_dir/ticket.stale1.lock" ]] || fail 'acquirable orphan ticket was not pruned'
[[ -e "$orphan_dir/ticket.held01.lock" ]] || fail 'held orphan ticket was pruned'
exec {held_orphan_fd}>&-
rm -f "$orphan_dir/ticket.held01.lock"
printf 'PASS: orphan pruning removes only acquirable ticket locks\n'

# ---- adaptive admission (BUILD_SLOT_FIXED unset, fake /proc/stat) ----
unset BUILD_SLOT_FIXED

# Writer keeps the fake stat "idle": only the idle field advances -> demand 0%
# -> full BUILD_SLOTS eligible. slot-0 is held, so an adaptive waiter must be
# willing to take slot-1.
idle_stat="$tmp/stat-idle"
printf 'cpu 100 0 100 1000 0 0 0 0 0 0\n' >"$idle_stat"
( i=1000; while :; do i=$((i + 500)); printf 'cpu 100 0 100 %s 0 0 0 0 0 0\n' "$i" >"$idle_stat.new"; mv "$idle_stat.new" "$idle_stat"; sleep 0.05; done ) &
idle_writer=$!
adaptive_dir="$tmp/adaptive-slots"
BUILD_SLOT_DIR="$adaptive_dir" BUILD_SLOTS=2 BUILD_SLOT_STAT="$idle_stat" "$slot" sleep 10 &
adaptive_holder=$!
sleep 0.5
BUILD_SLOT_DIR="$adaptive_dir" BUILD_SLOTS=2 BUILD_SLOT_STAT="$idle_stat" BUILD_SLOT_TIMEOUT=3 "$slot" true \
  || fail 'idle box did not admit a second slot'
printf 'PASS: adaptive grows to max when idle\n'

# Writer makes the fake stat "busy": user field advances -> demand ~100%
# -> only BUILD_SLOT_FLOOR (1) eligible; with slot-0 held the waiter must
# time out even though slot-1 is free.
busy_stat="$tmp/stat-busy"
printf 'cpu 1000 0 100 100 0 0 0 0 0 0\n' >"$busy_stat"
( i=1000; while :; do i=$((i + 500)); printf 'cpu %s 0 100 100 0 0 0 0 0 0\n' "$i" >"$busy_stat.new"; mv "$busy_stat.new" "$busy_stat"; sleep 0.05; done ) &
busy_writer=$!
set +e
busy_output="$(BUILD_SLOT_DIR="$adaptive_dir" BUILD_SLOTS=2 BUILD_SLOT_STAT="$busy_stat" BUILD_SLOT_TIMEOUT=1 "$slot" true 2>&1)"
busy_status=$?
set -e
(( busy_status != 0 )) || fail 'busy box admitted beyond the floor'
[[ "$busy_output" == *'timed out waiting for build slot'* ]] || fail "missing floor-timeout message: $busy_output"
printf 'PASS: adaptive shrinks to floor when interactive demand is high\n'

kill -9 "$idle_writer" "$busy_writer" "$adaptive_holder" 2>/dev/null || true
wait "$idle_writer" "$busy_writer" "$adaptive_holder" 2>/dev/null || true

# ---- cpu-guard heavy classification (agent path, default-pass) ----
export BUILD_SLOT_FIXED=1
fake="$tmp/fakebin"
mkdir "$fake"
printf '#!/usr/bin/env bash\ntouch "$RAN_MARKER"\n' >"$fake/vitest"
cp "$fake/vitest" "$fake/sometool"
chmod +x "$fake/vitest" "$fake/sometool"

class_dir="$tmp/class-slots"
BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_FIXED=1 "$slot" sleep 10 &
class_holder=$!
sleep 0.2

# heavy tool with the only slot held -> queues -> times out, never runs
set +e
RAN_MARKER="$tmp/heavy-ran" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
  AGENT_BUILD_SCOPE_ACTIVE=1 CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= "$guard" "$fake/vitest" run >/dev/null 2>&1
heavy_status=$?
set -e
(( heavy_status != 0 )) || fail 'heavy command bypassed the queue'
[[ ! -e "$tmp/heavy-ran" ]] || fail 'heavy command ran despite held slot'
printf 'PASS: heavy agent-tree command queues\n'

# unrecognized tool -> default-pass, runs immediately despite held slot
RAN_MARKER="$tmp/light-ran" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
  AGENT_BUILD_SCOPE_ACTIVE=1 CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= "$guard" "$fake/sometool" anything \
  || fail 'light command was queued or failed'
[[ -e "$tmp/light-ran" ]] || fail 'light command did not run'
printf 'PASS: light agent-tree command passes without queuing\n'

# real hook shapes (enumerated from settings.json/plugins/codex config) and a
# codex-CLI shape whose PROMPT token mentions a vitest path -> all must pass
cp "$fake/sometool" "$fake/node"
cp "$fake/sometool" "$fake/bun"
cp "$fake/sometool" "$fake/npx"
hook_shapes=(
  "$fake/node /home/user/.claude/hooks/stop-gate.mjs"
  "$fake/bun /home/user/.claude/plugins/marketplaces/context-mode/hooks/posttooluse.mjs"
  "$fake/npx -y context-mode hook cursor pretooluse"
  "$fake/node -e let d='';process.stdin.on('data',c=>d+=c)"
)
n=0
for shape in "${hook_shapes[@]}"; do
  n=$((n+1))
  RAN_MARKER="$tmp/hook-ran-$n" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
    AGENT_BUILD_SCOPE_ACTIVE=1 CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= "$guard" $shape \
    || fail "hook shape queued or failed: $shape"
  [[ -e "$tmp/hook-ran-$n" ]] || fail "hook shape did not run: $shape"
done
RAN_MARKER="$tmp/prompt-ran" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
  AGENT_BUILD_SCOPE_ACTIVE=1 CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= \
  "$guard" "$fake/node" /usr/local/bin/codex exec -m gpt-x "Fix the tests in apps/zync-app/vitest.config.ts and run vitest" \
  || fail 'codex-with-vitest-prompt was queued or failed'
[[ -e "$tmp/prompt-ran" ]] || fail 'codex-with-vitest-prompt did not run'
printf 'PASS: real hook shapes and prompt-token false positive all pass\n'

# standalone (non-agent) light command -> blacklist passes it uncapped/unqueued
RAN_MARKER="$tmp/standalone-ran" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
  AGENT_BUILD_SCOPE_ACTIVE= CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= "$guard" "$fake/sometool" --flag \
  || fail 'standalone light command was queued or failed'
[[ -e "$tmp/standalone-ran" ]] || fail 'standalone light command did not run'
printf 'PASS: standalone light command passes without queuing\n'

# watch mode of a heavy tool -> never queues
RAN_MARKER="$tmp/watch-ran" BUILD_SLOT_DIR="$class_dir" BUILD_SLOTS=1 BUILD_SLOT_TIMEOUT=1 \
  AGENT_BUILD_SCOPE_ACTIVE=1 CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= "$guard" "$fake/vitest" --watch \
  || fail 'watch mode was queued or failed'
[[ -e "$tmp/watch-ran" ]] || fail 'watch-mode command did not run'
printf 'PASS: watch mode passes without queuing\n'

kill -9 "$class_holder" 2>/dev/null || true
wait "$class_holder" 2>/dev/null || true

# systemd-run starts the scoped command from the user manager, outside the
# caller's process ancestry. Admission/reentrancy state must cross explicitly.
fake_systemd="$tmp/systemd-bin"
mkdir "$fake_systemd"
cat >"$fake_systemd/systemd-run" <<'EOF'
#!/usr/bin/env bash
if (( $# == 4 )) && [[ "$4" == true ]]; then exit 0; fi
environment=("HOME=$HOME")
command=()
while (( $# )); do
  case "$1" in
    -E)
      environment+=("$2")
      shift 2
      ;;
    --)
      shift
      command=("$@")
      break
      ;;
    *) shift ;;
  esac
done
exec env -i "${environment[@]}" "${command[@]}"
EOF
chmod +x "$fake_systemd/systemd-run"
PATH="$fake_systemd:$PATH" BUILD_SLOT_HELD=1 CPU_GUARD_ACTIVE=1 \
  "$HOME/.claude/lib/priority-run.sh" low true \
  || fail 'priority wrapper dropped build-slot reentrancy state at systemd boundary'
printf 'PASS: priority wrapper preserves build-slot reentrancy state\n'

# Exact production shape: heavy outer pnpm enters buildslot + priority-run,
# then a pnpm lifecycle script resolves pnpm through the PATH shim again.
nested_fixture="$tmp/nested-pnpm"
mkdir "$nested_fixture"
cp "$HOME/.claude/lib/testdata/nested-pnpm-package.json" "$nested_fixture/package.json"
cp "$HOME/.claude/lib/testdata/nested-pnpm-trace.sh" "$nested_fixture/trace.sh"
chmod +x "$nested_fixture/trace.sh"
nested_dir="$tmp/nested-slots"
for run in {1..10}; do
  nested_output="$(
    cd "$nested_fixture"
    PATH="$fake_systemd:$PATH" BUILD_SLOT_DIR="$nested_dir" BUILD_SLOTS=1 \
      BUILD_SLOT_FIXED=1 BUILD_SLOT_TIMEOUT=3 AGENT_BUILD_SCOPE_ACTIVE= \
      CPU_GUARD_ACTIVE= BUILD_SLOT_HELD= \
      timeout 10 "$HOME/.claude/bin/pnpm" run build 2>&1
  )" || fail "nested pnpm probe $run failed: $nested_output"
  [[ "$nested_output" == *'nested slot=1 cpu=1'* ]] \
    || fail "nested pnpm probe $run lost guard state: $nested_output"
done
printf 'PASS: 10/10 real nested pnpm wrapper probes\n'
printf 'PASS: buildslot tests\n'
