#!/usr/bin/env bash
# Benchmark hook latency with N policies.
#
# Usage: ./scripts/benchmark-hooks.sh [num_policies] [iterations]
# Defaults: 100 policies, 50 iterations
#
# Writes results to docs/benchmarks/hook-latency.md
set -euo pipefail

NUM_POLICIES="${1:-100}"
ITERATIONS="${2:-50}"
CACHE_FILE="$HOME/.clew/policy-cache.json"
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SESSION_HOOK="$SCRIPT_DIR/.claude/hooks/session-start.sh"
PRETOOL_HOOK="$SCRIPT_DIR/.claude/hooks/pre-tool-use.sh"
GO_BINARY="$SCRIPT_DIR/bin/clew-hook"
OUTPUT_FILE="$SCRIPT_DIR/docs/benchmarks/hook-latency.md"

mkdir -p "$(dirname "$OUTPUT_FILE")" "$HOME/.clew"

echo "Generating $NUM_POLICIES test policies..."

# Generate test policy cache
python3 -c "
import json
policies = []
for i in range($NUM_POLICIES):
    if i % 3 == 0:
        policies.append({
            'id': f'banned-import-{i}',
            'type': 'banned_import',
            'pattern': f'dangerous_module_{i}',
            'severity': 'block' if i % 5 == 0 else 'warn',
            'message': f'Test banned import rule {i}',
            'precept_id': None,
        })
    elif i % 3 == 1:
        policies.append({
            'id': f'protected-write-{i}',
            'type': 'protected_write',
            'pattern': f'src/protected_{i}/*',
            'severity': 'block' if i % 5 == 0 else 'warn',
            'message': f'Test protected write rule {i}',
            'precept_id': None,
        })
    else:
        policies.append({
            'id': f'unguarded-path-{i}',
            'type': 'unguarded_path',
            'pattern': f'src/unsafe_{i}/*',
            'severity': 'audit',
            'message': f'Test unguarded path rule {i}',
            'precept_id': None,
        })
print(json.dumps(policies))
" > "$CACHE_FILE"

echo "Running benchmarks ($ITERATIONS iterations each)..."

# Benchmark function: returns median in ms
benchmark() {
  local label="$1"
  local hook="$2"
  local input="$3"
  local timings=()

  for i in $(seq 1 "$ITERATIONS"); do
    start=$(python3 -c "import time; print(int(time.time_ns()))")
    echo "$input" | "$hook" > /dev/null 2>&1
    end=$(python3 -c "import time; print(int(time.time_ns()))")
    elapsed_ms=$(python3 -c "print(($end - $start) / 1_000_000)")
    timings+=("$elapsed_ms")
  done

  # Calculate stats with Python
  python3 -c "
import statistics
times = [float(t) for t in '''$(printf '%s\n' "${timings[@]}")'''.strip().split()]
times.sort()
p50 = statistics.median(times)
p95 = times[int(len(times) * 0.95)]
p99 = times[int(len(times) * 0.99)]
mean = statistics.mean(times)
print(f'{p50:.1f}|{p95:.1f}|{p99:.1f}|{mean:.1f}')
"
}

# Benchmark Go binary: takes label, binary path, subcommand, JSON input
benchmark_go() {
  local label="$1"
  local binary="$2"
  local subcmd="$3"
  local input="$4"
  local timings=()

  for i in $(seq 1 "$ITERATIONS"); do
    start=$(python3 -c "import time; print(int(time.time_ns()))")
    echo "$input" | "$binary" "$subcmd" > /dev/null 2>&1
    end=$(python3 -c "import time; print(int(time.time_ns()))")
    elapsed_ms=$(python3 -c "print(($end - $start) / 1_000_000)")
    timings+=("$elapsed_ms")
  done

  python3 -c "
import statistics
times = [float(t) for t in '''$(printf '%s\n' "${timings[@]}")'''.strip().split()]
times.sort()
p50 = statistics.median(times)
p95 = times[int(len(times) * 0.95)]
p99 = times[int(len(times) * 0.99)]
mean = statistics.mean(times)
print(f'{p50:.1f}|{p95:.1f}|{p99:.1f}|{mean:.1f}')
"
}

# Build Go binary if source exists
if [ -d "$SCRIPT_DIR/cmd/clew-hook" ]; then
  echo "Building Go binary..."
  (cd "$SCRIPT_DIR/cmd/clew-hook" && go build -o "$GO_BINARY" .)
  HAS_GO_BINARY=true
else
  HAS_GO_BINARY=false
fi

# Run benchmarks
echo "  PreToolUse (Bash, no violation)..."
PRETOOL_ALLOW=$(benchmark "pretool-allow" "$PRETOOL_HOOK" \
  '{"tool_name":"Bash","tool_input":{"command":"git status"}}')

echo "  PreToolUse (Bash, banned import match)..."
PRETOOL_DENY=$(benchmark "pretool-deny" "$PRETOOL_HOOK" \
  '{"tool_name":"Bash","tool_input":{"command":"import dangerous_module_0"}}')

echo "  PreToolUse (Write, protected path)..."
PRETOOL_WRITE=$(benchmark "pretool-write" "$PRETOOL_HOOK" \
  '{"tool_name":"Write","tool_input":{"file_path":"src/protected_1/config.py","content":"x=1"}}')

echo "  PreToolUse (Read, no checks)..."
PRETOOL_SKIP=$(benchmark "pretool-skip" "$PRETOOL_HOOK" \
  '{"tool_name":"Read","tool_input":{"file_path":"src/foo.py"}}')

echo "  SessionStart (no API)..."
SESSION_START=$(benchmark "session-start" "$SESSION_HOOK" \
  '{"session_id":"bench","hook_event_name":"SessionStart","source":"startup"}')

# Go binary benchmarks
if [ "$HAS_GO_BINARY" = true ]; then
  echo ""
  echo "Running Go binary benchmarks..."

  echo "  Go: PreToolUse (Bash, no violation)..."
  GO_PRETOOL_ALLOW=$(benchmark_go "go-pretool-allow" "$GO_BINARY" "pre-tool-use" \
    '{"tool_name":"Bash","tool_input":{"command":"git status"}}')

  echo "  Go: PreToolUse (Bash, banned import match)..."
  GO_PRETOOL_DENY=$(benchmark_go "go-pretool-deny" "$GO_BINARY" "pre-tool-use" \
    '{"tool_name":"Bash","tool_input":{"command":"import dangerous_module_0"}}')

  echo "  Go: PreToolUse (Write, protected path)..."
  GO_PRETOOL_WRITE=$(benchmark_go "go-pretool-write" "$GO_BINARY" "pre-tool-use" \
    '{"tool_name":"Write","tool_input":{"file_path":"src/protected_1/config.py","content":"x=1"}}')

  echo "  Go: PreToolUse (Read, no checks)..."
  GO_PRETOOL_SKIP=$(benchmark_go "go-pretool-skip" "$GO_BINARY" "pre-tool-use" \
    '{"tool_name":"Read","tool_input":{"file_path":"src/foo.py"}}')

  echo "  Go: SessionStart (no API)..."
  GO_SESSION_START=$(benchmark_go "go-session-start" "$GO_BINARY" "session-start" \
    '{"session_id":"bench"}')
fi

# Write results
cat > "$OUTPUT_FILE" << EOF
# Hook Latency Benchmark

**Date**: $(date -u '+%Y-%m-%d %H:%M UTC')
**Policies**: $NUM_POLICIES
**Iterations**: $ITERATIONS per scenario
**Platform**: $(uname -s) $(uname -m)

## Results

| Scenario | p50 (ms) | p95 (ms) | p99 (ms) | Mean (ms) |
|----------|----------|----------|----------|-----------|
| PreToolUse: Bash allow (no match) | $(echo "$PRETOOL_ALLOW" | sed 's/|/ | /g') |
| PreToolUse: Bash deny (banned import) | $(echo "$PRETOOL_DENY" | sed 's/|/ | /g') |
| PreToolUse: Write deny (protected path) | $(echo "$PRETOOL_WRITE" | sed 's/|/ | /g') |
| PreToolUse: Read skip (no checks) | $(echo "$PRETOOL_SKIP" | sed 's/|/ | /g') |
| SessionStart (API unreachable) | $(echo "$SESSION_START" | sed 's/|/ | /g') |

$(if [ "$HAS_GO_BINARY" = true ]; then cat << GOEOF
## Go Binary Results

| Scenario | p50 (ms) | p95 (ms) | p99 (ms) | Mean (ms) |
|----------|----------|----------|----------|-----------|
| Go PreToolUse: Bash allow (no match) | $(echo "$GO_PRETOOL_ALLOW" | sed 's/|/ | /g') |
| Go PreToolUse: Bash deny (banned import) | $(echo "$GO_PRETOOL_DENY" | sed 's/|/ | /g') |
| Go PreToolUse: Write deny (protected path) | $(echo "$GO_PRETOOL_WRITE" | sed 's/|/ | /g') |
| Go PreToolUse: Read skip (no checks) | $(echo "$GO_PRETOOL_SKIP" | sed 's/|/ | /g') |
| Go SessionStart (API unreachable) | $(echo "$GO_SESSION_START" | sed 's/|/ | /g') |
GOEOF
fi)

## Targets

- **PreToolUse**: <50ms p95 (shell), <10ms p95 (Go)
- **SessionStart**: <3s (with live API)

## Notes

- SessionStart benchmark uses offline mode (API unreachable, curl timeout 5s skipped).
- Shell PreToolUse benchmarks include jq parsing of $NUM_POLICIES policies.
- Go binary uses native JSON parsing with in-memory policy cache.
- All hooks fail-open on error (exit 1 = non-blocking).
EOF

echo ""
echo "Results written to $OUTPUT_FILE"
cat "$OUTPUT_FILE"
