#!/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'

# ---- 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

printf 'PASS: buildslot tests\n'
