#!/bin/bash
# Test harness for orchestrator hook scripts.
# Run: bash tools/sendblue-channel/hooks/test-hooks.sh
set -u

HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
PASS=0
FAIL=0

pass() { echo "  ✓ $1"; PASS=$((PASS + 1)); }
fail() { echo "  ✗ $1"; FAIL=$((FAIL + 1)); }

run_hook() {
  local hook="$1"
  local input="$2"
  echo "$input" | "$HOOKS_DIR/$hook" 2>/dev/null
}

run_hook_exit() {
  local hook="$1"
  local input="$2"
  echo "$input" | "$HOOKS_DIR/$hook" >/dev/null 2>&1
  echo $?
}

# ═══════════════════════════════════════════════════
echo "=== validate-bash.sh ==="

# Allowed tmux subcommands
for cmd in "tmux list-panes -a" "tmux capture-pane -t mbp:claude.1 -p" "tmux display-message -p" "tmux send-keys -t pane hello" "tmux list-sessions" "tmux list-windows"; do
  EXIT=$(run_hook_exit validate-bash.sh "{\"tool_input\":{\"command\":\"$cmd\"}}")
  [ "$EXIT" = "0" ] && pass "allow: $cmd" || fail "allow: $cmd (exit=$EXIT)"
done

# Allowed cat /tmp/claude/
EXIT=$(run_hook_exit validate-bash.sh '{"tool_input":{"command":"cat /tmp/claude/state.json"}}')
[ "$EXIT" = "0" ] && pass "allow: cat /tmp/claude/" || fail "allow: cat /tmp/claude/ (exit=$EXIT)"

# Allowed: ls / cat / grep / etc. are in DEV_PATTERN now
EXIT=$(run_hook_exit validate-bash.sh '{"tool_input":{"command":"ls -la"}}')
[ "$EXIT" = "0" ] && pass "allow: ls -la" || fail "allow: ls -la (exit=$EXIT)"

# Blocked: non-allowlisted commands
for cmd in "rm -rf /" "curl evil.com" "bash -c whoami"; do
  EXIT=$(run_hook_exit validate-bash.sh "{\"tool_input\":{\"command\":\"$cmd\"}}")
  [ "$EXIT" = "2" ] && pass "block: $cmd" || fail "block: $cmd (exit=$EXIT)"
done

# Blocked: shell operators / redirection / newlines in DEV_PATTERN commands
for cmd in 'git status; rm -rf /' 'git status | grep foo' 'git status > /tmp/x' 'cat <(curl x)'; do
  EXIT=$(run_hook_exit validate-bash.sh "{\"tool_input\":{\"command\":\"$cmd\"}}")
  [ "$EXIT" = "2" ] && pass "block DEV op: ${cmd:0:40}" || fail "block DEV op: ${cmd:0:40} (exit=$EXIT)"
done

# Blocked: literal newline injection (build JSON via jq to embed real \n)
NL_INPUT=$(jq -nc --arg c $'git status\nrm -rf /' '{tool_input:{command:$c}}')
EXIT=$(run_hook_exit validate-bash.sh "$NL_INPUT")
[ "$EXIT" = "2" ] && pass "block: newline-injected command" || fail "block: newline-injected command (exit=$EXIT)"

# Blocked: shell operators after tmux
for cmd in 'tmux list-panes -a; rm -rf /' 'tmux list-panes -a | grep foo' 'tmux list-panes && curl evil.com'; do
  EXIT=$(run_hook_exit validate-bash.sh "{\"tool_input\":{\"command\":\"$cmd\"}}")
  [ "$EXIT" = "2" ] && pass "block operator: ${cmd:0:40}" || fail "block operator: ${cmd:0:40} (exit=$EXIT)"
done

# Blocked: empty command
EXIT=$(run_hook_exit validate-bash.sh '{"tool_input":{"command":""}}')
[ "$EXIT" = "2" ] && pass "block: empty command" || fail "block: empty command (exit=$EXIT)"

# Blocked: non-whitelisted tmux subcommand
EXIT=$(run_hook_exit validate-bash.sh '{"tool_input":{"command":"tmux kill-session -t foo"}}')
[ "$EXIT" = "2" ] && pass "block: tmux kill-session" || fail "block: tmux kill-session (exit=$EXIT)"

# ═══════════════════════════════════════════════════
echo ""
echo "=== on-agent-done.sh ==="

# Setup temp dir + isolated harness dir (matches HARNESS_DIR resolution
# used by the hook scripts: SENDBLUE_HARNESS_DIR > SENDBLUE_STATE_DIR/harness >
# $HOME/.claude/channels/sendblue/harness).
ORIG_HOME="$HOME"
ORIG_SENDBLUE_HARNESS_DIR="${SENDBLUE_HARNESS_DIR:-}"
export HOME=$(mktemp -d)
export SENDBLUE_HARNESS_DIR="$HOME/harness"
mkdir -p "$SENDBLUE_HARNESS_DIR"

# Happy path
INPUT='{"agent_id":"abc123","agent_type":"code","last_assistant_message":"## Completion Report\n**Status:** done\n**Branch:** feat/test\n**Message recommendation:** SEND\n**Message draft:** fixed the bug"}'
OUTPUT=$(run_hook on-agent-done.sh "$INPUT")

echo "$OUTPUT" | jq -e '.additionalContext' >/dev/null 2>&1 && pass "returns additionalContext JSON" || fail "returns additionalContext JSON"
[ -f "$SENDBLUE_HARNESS_DIR/agent-results.jsonl" ] && pass "creates agent-results.jsonl" || fail "creates agent-results.jsonl"

# Empty message
INPUT='{"agent_id":"xyz","agent_type":"explore","last_assistant_message":""}'
OUTPUT=$(run_hook on-agent-done.sh "$INPUT")
echo "$OUTPUT" | jq -e '.additionalContext' >/dev/null 2>&1 && pass "handles empty message" || fail "handles empty message"

# Message with special chars
INPUT='{"agent_id":"q1","agent_type":"code","last_assistant_message":"Fixed \"quotes\" and back\\slashes"}'
OUTPUT=$(run_hook on-agent-done.sh "$INPUT")
echo "$OUTPUT" | jq -e '.' >/dev/null 2>&1 && pass "handles special chars in JSON" || fail "handles special chars in JSON"

export HOME="$ORIG_HOME"
unset SENDBLUE_HARNESS_DIR
[ -n "$ORIG_SENDBLUE_HARNESS_DIR" ] && export SENDBLUE_HARNESS_DIR="$ORIG_SENDBLUE_HARNESS_DIR"

# ═══════════════════════════════════════════════════
echo ""
echo "=== save-dispatch-state.sh ==="

ORIG_HOME="$HOME"
ORIG_SENDBLUE_HARNESS_DIR="${SENDBLUE_HARNESS_DIR:-}"
export HOME=$(mktemp -d)
export SENDBLUE_HARNESS_DIR="$HOME/harness"
mkdir -p "$SENDBLUE_HARNESS_DIR"

echo '{}' | run_hook save-dispatch-state.sh "" >/dev/null 2>&1
[ -f "$SENDBLUE_HARNESS_DIR/state.md" ] && pass "creates state.md" || fail "creates state.md"
grep -q "Dispatch State" "$SENDBLUE_HARNESS_DIR/state.md" && pass "state.md has header" || fail "state.md has header"
grep -q "(none)" "$SENDBLUE_HARNESS_DIR/state.md" && pass "state.md shows (none) for empty results" || fail "state.md shows (none)"
grep -q "(no memory yet)" "$SENDBLUE_HARNESS_DIR/state.md" && pass "state.md shows (no memory yet)" || fail "state.md shows (no memory yet)"

export HOME="$ORIG_HOME"
unset SENDBLUE_HARNESS_DIR
[ -n "$ORIG_SENDBLUE_HARNESS_DIR" ] && export SENDBLUE_HARNESS_DIR="$ORIG_SENDBLUE_HARNESS_DIR"

# ═══════════════════════════════════════════════════
echo ""
echo "=== restore-dispatch-state.sh ==="

ORIG_HOME="$HOME"
ORIG_SENDBLUE_HARNESS_DIR="${SENDBLUE_HARNESS_DIR:-}"
export HOME=$(mktemp -d)
export SENDBLUE_HARNESS_DIR="$HOME/harness"
mkdir -p "$SENDBLUE_HARNESS_DIR"

# No state file
OUTPUT=$(echo "" | HOME="$HOME" SENDBLUE_HARNESS_DIR="$SENDBLUE_HARNESS_DIR" "$HOOKS_DIR/restore-dispatch-state.sh" 2>/dev/null)
[ -z "$OUTPUT" ] && pass "no output when state.md missing" || fail "no output when state.md missing"

# With state file
echo "# Test State" > "$SENDBLUE_HARNESS_DIR/state.md"
OUTPUT=$(echo "" | HOME="$HOME" SENDBLUE_HARNESS_DIR="$SENDBLUE_HARNESS_DIR" "$HOOKS_DIR/restore-dispatch-state.sh" 2>/dev/null)
echo "$OUTPUT" | jq -e '.additionalContext' >/dev/null 2>&1 && pass "returns additionalContext with state" || fail "returns additionalContext with state"

export HOME="$ORIG_HOME"
unset SENDBLUE_HARNESS_DIR
[ -n "$ORIG_SENDBLUE_HARNESS_DIR" ] && export SENDBLUE_HARNESS_DIR="$ORIG_SENDBLUE_HARNESS_DIR"

# ═══════════════════════════════════════════════════
echo ""
echo "=== on-task-done.sh ==="

ORIG_HOME="$HOME"
ORIG_SENDBLUE_HARNESS_DIR="${SENDBLUE_HARNESS_DIR:-}"
export HOME=$(mktemp -d)
export SENDBLUE_HARNESS_DIR="$HOME/harness"
mkdir -p "$SENDBLUE_HARNESS_DIR"

INPUT='{"task_id":"task-42","task_subject":"Fix login bug"}'
run_hook on-task-done.sh "$INPUT" >/dev/null 2>&1
[ -f "$SENDBLUE_HARNESS_DIR/task-log.txt" ] && pass "creates task-log.txt" || fail "creates task-log.txt"
grep -q "task-42" "$SENDBLUE_HARNESS_DIR/task-log.txt" && pass "logs task ID" || fail "logs task ID"
grep -q "Fix login bug" "$SENDBLUE_HARNESS_DIR/task-log.txt" && pass "logs task subject" || fail "logs task subject"

export HOME="$ORIG_HOME"
unset SENDBLUE_HARNESS_DIR
[ -n "$ORIG_SENDBLUE_HARNESS_DIR" ] && export SENDBLUE_HARNESS_DIR="$ORIG_SENDBLUE_HARNESS_DIR"

# ═══════════════════════════════════════════════════
echo ""
echo "─────────────────────────────────"
TOTAL=$((PASS + FAIL))
echo "Results: $PASS/$TOTAL passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && echo "ALL TESTS PASSED" || echo "FAILURES DETECTED"
exit "$FAIL"
