#!/usr/bin/env bash
# Verdict regression test for the .sh hook fast-path prefilters
# (quietcontext-nudge.sh, fs-scan-gate.sh, main-checkout-guard.sh,
# worktree-lock-gate.sh, bg-gate.sh, bash-gate.sh). Each case asserts the exact
# (exit code, allow/deny) verdict the hook produced before the prefilters were
# added — a prefilter regression (over-broad exit 0) shows up here as a flipped
# verdict, not just a slower run. No framework: plain bash, exit non-zero on
# any mismatch.
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOOKS="$HERE/.."
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
FT_BIN="$WORK/ft-bin"
mkdir -p "$FT_BIN"
cat > "$FT_BIN/ft" <<'EOF'
#!/usr/bin/env bash
exec "$@"
EOF
chmod +x "$FT_BIN/ft"
PATH="$FT_BIN:$PATH"
export PATH

FAKE="$WORK/fakerepo"
mkdir -p "$FAKE"
git -C "$FAKE" init -q
git -C "$FAKE" config user.email hook-test@example.invalid
git -C "$FAKE" config user.name hook-test
git -C "$FAKE" commit -q --allow-empty -m init
git -C "$FAKE" worktree add -q "$FAKE/.worktrees/demo" -b wt/demo >/dev/null 2>&1
DEMO="$FAKE/.worktrees/demo"
mkdir -p "$FAKE/.worktrees/.locks"

FAIL=0
assert() {
  local desc="$1" hook="$2" payload="$3" want_rc="$4" want_deny="$5" env_extra="${6:-}"
  local out rc got_deny
  out=$(printf '%s' "$payload" | env $env_extra bash "$HOOKS/$hook" 2>&1)
  rc=$?
  [[ -n "$out" ]] && got_deny=yes || got_deny=no
  if [[ "$rc" != "$want_rc" || "$got_deny" != "$want_deny" ]]; then
    echo "FAIL [$hook] $desc :: want rc=$want_rc deny=$want_deny, got rc=$rc deny=$got_deny"
    echo "  payload=$payload"
    echo "  output=$out"
    FAIL=1
  fi
}

bash_payload() {
  printf '{"tool_name":"Bash","tool_input":{"command":%s}}' \
    "$(printf '%s' "$1" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read().rstrip(chr(10))))')"
}

bash_cwd_payload() {
  local cwd="$1" cmd="$2"
  printf '{"tool_name":"Bash","cwd":%s,"tool_input":{"command":%s}}' \
    "$(python3 -c "import json;print(json.dumps('$cwd'))")" \
    "$(printf '%s' "$cmd" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read().rstrip(chr(10))))')"
}

edit_payload() {
  local tool="$1" cwd="$2" target="$3"
  printf '{"tool_name":"%s","cwd":%s,"tool_input":{"file_path":%s}}' "$tool" \
    "$(python3 -c "import json;print(json.dumps('$cwd'))")" \
    "$(python3 -c "import json;print(json.dumps('$target'))")"
}

# --- quietcontext-nudge.sh ---
H=quietcontext-nudge.sh
assert "benign passthrough" "$H" "$(bash_payload 'echo hi')" 0 no
assert "find denied" "$H" "$(bash_payload 'find . -name x')" 0 yes
assert "find raw-ok bypass" "$H" "$(bash_payload 'find . -name x # raw-ok')" 0 no
assert "find + rm excluded" "$H" "$(bash_payload 'find . -exec rm {} \;')" 0 no
assert "find piped to wc bounded" "$H" "$(bash_payload 'find . -name x | wc -l')" 0 no
assert "recursive grep denied" "$H" "$(bash_payload 'grep -rn foo .')" 0 yes
assert "glob cat denied" "$H" "$(bash_payload 'cat *.md')" 0 yes

# --- fs-scan-gate.sh ---
H=fs-scan-gate.sh
assert "root find denied" "$H" "$(bash_payload 'find /')" 0 yes
assert "scoped find allowed" "$H" "$(bash_payload 'find /tmp')" 0 no
assert "ft find / denied" "$H" "$(bash_payload 'ft find /')" 0 yes
assert "quoted root denied" "$H" "$(bash_payload 'find "/"')" 0 yes
assert "dot allowed" "$H" "$(bash_payload 'find .')" 0 no
assert "no find token" "$H" "$(bash_payload 'echo hi')" 0 no

# --- bash-gate.sh ---
H=bash-gate.sh
assert "benign passthrough" "$H" "$(bash_payload 'echo hi')" 0 no
assert "git log rewritten" "$H" "$(bash_payload 'git log')" 0 yes
assert "redirect passthrough" "$H" "$(bash_payload 'cat a b > c')" 0 no
assert "find rewritten" "$H" "$(bash_payload 'find . -name x')" 0 yes
assert "cat rewritten" "$H" "$(bash_payload 'cat bigfile.log')" 0 yes
assert "git status never rewritten" "$H" "$(bash_payload 'git status')" 0 no
assert "git status --porcelain never rewritten" "$H" "$(bash_payload 'git status --porcelain')" 0 no
assert "grep never rewritten" "$H" "$(bash_payload 'grep -rn foo .')" 0 no
assert "rg never rewritten" "$H" "$(bash_payload 'rg -n foo .')" 0 no
assert "heredoc never rewritten" "$H" "$(bash_payload $'python3 - <<'"'"'EOF'"'"'\nprint("git status")\nEOF')" 0 no
assert "here-string never rewritten" "$H" "$(bash_payload 'cat <<< "git status"')" 0 no

assert_rewrite() {
  local desc="$1" cmd="$2" want_cmd="$3"
  local out got_cmd
  out=$(printf '%s' "$(bash_payload "$cmd")" | bash "$HOOKS/bash-gate.sh" 2>&1)
  got_cmd=$(printf '%s' "$out" | python3 -c 'import json,sys;print(json.load(sys.stdin)["hookSpecificOutput"]["updatedInput"]["command"])' 2>/dev/null)
  if [[ "$got_cmd" != "$want_cmd" ]]; then
    echo "FAIL [bash-gate.sh rewrite] $desc :: want=[$want_cmd] got=[$got_cmd]"
    FAIL=1
  fi
}
assert_rewrite "git log exact rewrite" 'git log' 'FT_FROM_HOOK=1 FT_HINTS=1 ft git log'
assert_rewrite "find exact rewrite" 'find . -name x' 'FT_FROM_HOOK=1 FT_HINTS=1 ft find . -name x'
assert_rewrite "cat exact rewrite" 'cat bigfile.log' 'FT_FROM_HOOK=1 FT_HINTS=1 ft cat bigfile.log'

# Git content reads are agent input, not summaries: routing them through ft can
# silently truncate the bytes the agent sees.  Make the failure deterministic
# with an ft fixture that retains only 127 lines, then execute each command
# after its real bash-gate rewrite decision.
CONTENT_REPO="$WORK/content-repo"
CONTENT_BIN="$FT_BIN"
mkdir -p "$CONTENT_REPO"
git -C "$CONTENT_REPO" init -q
git -C "$CONTENT_REPO" config user.email hook-test@example.invalid
git -C "$CONTENT_REPO" config user.name hook-test
git -C "$CONTENT_REPO" commit -q --allow-empty -m initial
for n in $(seq 1 906); do printf 'line %04d: content must survive the gate\n' "$n"; done > "$CONTENT_REPO/large.txt"
git -C "$CONTENT_REPO" add large.txt
git -C "$CONTENT_REPO" commit -q -m large-content
cat > "$CONTENT_BIN/ft" <<'EOF'
#!/usr/bin/env bash
"$@" | sed -n '1,127p'
EOF
chmod +x "$CONTENT_BIN/ft"

rewritten_or_original() {
  local cmd="$1" out
  out=$(printf '%s' "$(bash_payload "$cmd")" | env PATH="$CONTENT_BIN:$PATH" bash "$HOOKS/bash-gate.sh")
  python3 -c '
import json, sys
payload = sys.stdin.read()
print(json.loads(payload)["hookSpecificOutput"]["updatedInput"]["command"] if payload.strip() else sys.argv[1])
' "$cmd" <<<"$out"
}

assert_git_content_passthrough() {
  local desc="$1" cmd="$2" raw_file got_file rewritten
  raw_file="$WORK/raw-content"
  got_file="$WORK/gated-content"
  git -C "$CONTENT_REPO" $cmd > "$raw_file" || { echo "FAIL [bash-gate.sh content] $desc :: raw command failed"; FAIL=1; return; }
  rewritten=$(rewritten_or_original "cd '$CONTENT_REPO' && git $cmd")
  env PATH="$CONTENT_BIN:$PATH" bash -c "$rewritten" > "$got_file" || { echo "FAIL [bash-gate.sh content] $desc :: rewritten command failed"; FAIL=1; return; }
  if ! cmp -s "$raw_file" "$got_file"; then
    echo "FAIL [bash-gate.sh content] $desc :: bytes changed (raw bytes=$(wc -c < "$raw_file"), got bytes=$(wc -c < "$got_file"))"
    FAIL=1
  fi
}

assert_git_content_passthrough "git show preserves 906-line blob" "show HEAD:large.txt"
assert_git_content_passthrough "git diff preserves 906-line patch" "diff HEAD^ HEAD -- large.txt"
assert_git_content_passthrough "git cat-file preserves 906-line blob" "cat-file blob HEAD:large.txt"

# --- bg-gate.sh (control off by default) ---
H=bg-gate.sh
assert "control off: bg allowed" "$H" "$(bash_payload 'sleep 1 &')" 0 no
assert "control off: benign" "$H" "$(bash_payload 'echo hi')" 0 no
CTRL_TRUE="$WORK/ctrl-true"
mkdir -p "$CTRL_TRUE"
printf '{"version":"hook-controls/v1","hooks":{"background-jobs-blocker":true}}' > "$CTRL_TRUE/hook-controls.json"
assert "control on: bg denied" "$H" "$(bash_payload 'sleep 1 &')" 0 yes "OVERDECK_CONFIG_DIR=$CTRL_TRUE"
assert "control on: benign" "$H" "$(bash_payload 'echo hi')" 0 no "OVERDECK_CONFIG_DIR=$CTRL_TRUE"

# --- main-checkout-guard.sh ---
H=main-checkout-guard.sh
assert "benign bash" "$H" "$(bash_cwd_payload "$DEMO" 'echo hi')" 0 no
assert "git restore inside worktree allowed" "$H" "$(bash_cwd_payload "$DEMO" 'git restore .')" 0 no
assert "git restore in main root denied" "$H" "$(bash_cwd_payload "$FAKE" 'git restore .')" 0 yes
assert "git checkout . in main root denied" "$H" "$(bash_cwd_payload "$FAKE" 'git checkout .')" 0 yes
assert "write to demo allowed" "$H" "$(bash_cwd_payload "$DEMO" "echo hi > $DEMO/f.txt")" 0 no
assert "write to fake main root denied" "$H" "$(bash_cwd_payload "$DEMO" "echo hi > $FAKE/f.txt")" 0 yes
assert "sed -i to main root denied" "$H" "$(bash_cwd_payload "$DEMO" "sed -i s/a/b/ $FAKE/f.txt")" 0 yes
assert "tee to main root denied" "$H" "$(bash_cwd_payload "$DEMO" "tee $FAKE/f.txt")" 0 yes
assert "edit within worktree allowed" "$H" "$(edit_payload Edit "$DEMO" "$DEMO/ok.txt")" 0 no
assert "edit main root denied" "$H" "$(edit_payload Edit "$DEMO" "$FAKE/protected.txt")" 0 yes
assert "write main root denied" "$H" "$(edit_payload Write "$DEMO" "$FAKE/protected.txt")" 0 yes

# --- worktree-lock-gate.sh ---
H=worktree-lock-gate.sh
assert "non-destructive bash allowed" "$H" "$(printf '{"tool_name":"Bash","cwd":%s,"session_id":"s1","tool_input":{"command":%s}}' "$(python3 -c "import json;print(json.dumps('$DEMO'))")" "$(printf 'git status' | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')")" 0 no
assert "destructive git claims lock (first owner)" "$H" "$(printf '{"tool_name":"Bash","cwd":%s,"session_id":"s1","tool_input":{"command":%s}}' "$(python3 -c "import json;print(json.dumps('$DEMO'))")" "$(printf 'git restore .' | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')")" 0 no

# Second, DIFFERENT session while the owner (s1) is still "alive" (simulated /proc) must
# be denied — proves the prefilter's fall-through still reaches the real deny path.
FAKEPROC="$WORK/fakeproc"
mkdir -p "$FAKEPROC/999/fd"
printf 'claude' > "$FAKEPROC/999/comm"
ln -s "/fake/transcripts/s1.jsonl" "$FAKEPROC/999/fd/0"
assert "second session denied while owner alive" "$H" "$(printf '{"tool_name":"Bash","cwd":%s,"session_id":"s2","tool_input":{"command":%s}}' "$(python3 -c "import json;print(json.dumps('$DEMO'))")" "$(printf 'git restore .' | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')")" 0 yes "WT_LOCK_PROC_DIR=$FAKEPROC"

# --- socket stdin (Claude Code hands hooks a socketpair; open("/dev/stdin") on a
# socket fails ENXIO, so payload reads must use the read builtin, never $(</dev/stdin)) ---
for H in quietcontext-nudge.sh fs-scan-gate.sh main-checkout-guard.sh worktree-lock-gate.sh bg-gate.sh bash-gate.sh; do
  err=$(python3 - "$HOOKS/$H" <<'PYEOF' 2>&1
import socket, subprocess, sys
a, b = socket.socketpair()
b.send(b'{"tool_name":"Bash","tool_input":{"command":"true"},"cwd":"/tmp"}')
b.shutdown(socket.SHUT_WR)
r = subprocess.run(["bash", sys.argv[1]], stdin=a.fileno(), capture_output=True, text=True, timeout=30)
sys.stderr.write(r.stderr)
sys.exit(1 if "/dev/stdin" in r.stderr else 0)
PYEOF
  ) || { echo "FAIL [$H] socket stdin :: $err"; FAIL=1; }
done

if [[ $FAIL -eq 0 ]]; then
  echo "sh-fastpath-verdicts: ALL PASS"
  exit 0
fi
exit 1
