#!/usr/bin/env bash
# End-to-end proof: PreToolUse hook -> collector queue -> action gateway verdict -> hook output.
set -uo pipefail

WT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
HOOK="$WT/modules/workstation/claude/hooks/browser-approval.sh"
PORT=4991
TMP=$(mktemp -d /tmp/od-perm-e2e-XXXX)
export OVERDECK_CONFIG_DIR="$TMP/config"
export OVERDECK_STATE_DIR="$TMP/state"
mkdir -p "$OVERDECK_CONFIG_DIR" "$OVERDECK_STATE_DIR"
printf 'port = %s\n' "$PORT" >"$OVERDECK_CONFIG_DIR/config.toml"

fail() { echo "FAIL: $*"; cleanup; exit 1; }
cleanup() { [[ -n ${COLLECTOR_PID:-} ]] && kill "$COLLECTOR_PID" 2>/dev/null; rm -rf "$TMP"; }

cd "$WT/collector"
bun src/index.ts >"$TMP/collector.log" 2>&1 &
COLLECTOR_PID=$!

for _ in $(seq 60); do
  [[ -f "$OVERDECK_CONFIG_DIR/token" ]] && curl -sf -H "authorization: Bearer $(cat "$OVERDECK_CONFIG_DIR/token")" \
    "http://127.0.0.1:$PORT/health" >/dev/null && break
  sleep 0.2
done
TOKEN=$(cat "$OVERDECK_CONFIG_DIR/token") || fail "no token"
curl -sf -H "authorization: Bearer $TOKEN" "http://127.0.0.1:$PORT/health" >/dev/null || fail "collector did not start ($(cat "$TMP/collector.log"))"
echo "ok: collector up on $PORT"

api() { curl -sS -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' "$@"; }

PAYLOAD='{"session_id":"e2e-session","tool_name":"Bash","tool_input":{"command":"rm -rf /nope"},"cwd":"/home/user/Projects/overdeck"}'

# --- 1. disarmed: hook must stay silent and must not enqueue anything
OUT=$(echo "$PAYLOAD" | "$HOOK"); RC=$?
[[ $RC -eq 0 && -z $OUT ]] || fail "disarmed hook produced rc=$RC out=$OUT"
echo "ok: disarmed hook is silent"

# --- 2. arm from the "browser"
api -X POST -d '{"args":{"state":"on"},"requestedBy":"e2e"}' "http://127.0.0.1:$PORT/actions/permission.arm" >/dev/null
[[ -f "$OVERDECK_CONFIG_DIR/browser-approvals.json" ]] || fail "arm file not written"
[[ $(stat -c %a "$OVERDECK_CONFIG_DIR/browser-approvals.json") == 600 ]] || fail "arm file is not 0600"
echo "ok: armed, arm file 0600"

# --- 3. armed + allow
echo "$PAYLOAD" | "$HOOK" >"$TMP/allow.out" 2>"$TMP/allow.err" &
HOOK_PID=$!
REQ=""
for _ in $(seq 100); do
  REQ=$(api "http://127.0.0.1:$PORT/items?kind=decision" | grep -o '"id":"permission:[^"]*"' | head -1 | cut -d'"' -f4)
  [[ -n $REQ ]] && break
  sleep 0.1
done
[[ -n $REQ ]] || fail "no pending permission item appeared"
echo "ok: pending item $REQ visible to the browser"
api -X POST -d "{\"args\":{\"requestId\":\"$REQ\",\"choice\":\"allow\"},\"requestedBy\":\"deck-web\"}" \
  "http://127.0.0.1:$PORT/actions/permission.answer" >"$TMP/answer.json"
grep -q '"ok":true' "$TMP/answer.json" || fail "answer rejected: $(cat "$TMP/answer.json")"
wait $HOOK_PID
grep -q '"permissionDecision":"allow"' "$TMP/allow.out" || fail "hook did not emit allow: $(cat "$TMP/allow.out")"
echo "ok: allow reached the hook -> $(cat "$TMP/allow.out")"

[[ $(api "http://127.0.0.1:$PORT/items?kind=decision" | grep -c 'permission:') -eq 0 ]] \
  || fail "answered request still pending"
echo "ok: answered request left the queue"

# --- 4. armed + deny
echo "$PAYLOAD" | "$HOOK" >"$TMP/deny.out" 2>&1 &
HOOK_PID=$!
REQ=""
for _ in $(seq 100); do
  REQ=$(api "http://127.0.0.1:$PORT/items?kind=decision" | grep -o '"id":"permission:[^"]*"' | head -1 | cut -d'"' -f4)
  [[ -n $REQ ]] && break
  sleep 0.1
done
[[ -n $REQ ]] || fail "no pending item for deny"
api -X POST -d "{\"args\":{\"requestId\":\"$REQ\",\"choice\":\"deny\"},\"requestedBy\":\"deck-web\"}" \
  "http://127.0.0.1:$PORT/actions/permission.answer" >/dev/null
wait $HOOK_PID
grep -q '"permissionDecision":"deny"' "$TMP/deny.out" || fail "hook did not emit deny: $(cat "$TMP/deny.out")"
echo "ok: deny reached the hook"

# --- 5. armed, nobody answers: bounded wait, silent fallthrough, queue drains on disconnect
python3 - "$OVERDECK_CONFIG_DIR/browser-approvals.json" waitMs 1000 <<'PY'
import json, sys
p, key, value = sys.argv[1], sys.argv[2], int(sys.argv[3])
d = json.load(open(p))
d[key] = value
open(p, "w").write(json.dumps(d))
PY
START=$(date +%s%N)
OUT=$(echo "$PAYLOAD" | "$HOOK"); RC=$?
ELAPSED=$(( ($(date +%s%N) - START) / 1000000 ))
[[ $RC -eq 0 && -z $OUT ]] || fail "unanswered hook produced rc=$RC out=$OUT"
(( ELAPSED >= 5000 && ELAPSED < 12000 )) || fail "unanswered wait was ${ELAPSED}ms, expected the ~6s curl bound"
echo "ok: unanswered call fell back to the terminal after ${ELAPSED}ms, no directive emitted"
sleep 0.5
[[ $(api "http://127.0.0.1:$PORT/items?kind=decision" | grep -c 'permission:') -eq 0 ]] \
  || fail "queue kept an item whose agent had already disconnected"
echo "ok: a disconnected agent's request left the queue"

# --- 6. expired arm window behaves as disarmed
python3 - "$OVERDECK_CONFIG_DIR/browser-approvals.json" until 1000 <<'PY'
import json, sys
p, key, value = sys.argv[1], sys.argv[2], int(sys.argv[3])
d = json.load(open(p))
d[key] = value
open(p, "w").write(json.dumps(d))
PY
START=$(date +%s%N)
OUT=$(echo "$PAYLOAD" | "$HOOK"); RC=$?
ELAPSED=$(( ($(date +%s%N) - START) / 1000000 ))
[[ $RC -eq 0 && -z $OUT ]] || fail "expired-window hook produced rc=$RC out=$OUT"
(( ELAPSED < 500 )) || fail "expired window still cost ${ELAPSED}ms"
echo "ok: expired arm window is treated as disarmed (${ELAPSED}ms)"

# --- 7. disarm removes the file
api -X POST -d '{"args":{"state":"off"},"requestedBy":"e2e"}' "http://127.0.0.1:$PORT/actions/permission.arm" >/dev/null
[[ -f "$OVERDECK_CONFIG_DIR/browser-approvals.json" ]] && fail "disarm left the arm file"
echo "ok: disarm removed the arm file"

grep -q 'permission.arm' "$OVERDECK_STATE_DIR/actions.jsonl" && echo "ok: arm/disarm journalled"
grep -q 'permission.answer' "$OVERDECK_STATE_DIR/actions.jsonl" && echo "ok: verdicts journalled"

echo "ALL PASS"
cleanup
