#!/usr/bin/env bash
# Deterministic regression harness for claude_cli_runner.sh wrapper behavior.
# Uses a fake `claude` binary, so it does not require network/auth/model access.
set -euo pipefail

# Resolve the runner relative to this script's own directory so the regression
# works after npm/Pi installation, regardless of where the package landed on disk.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WRAPPER="${WRAPPER:-$SCRIPT_DIR/claude_cli_runner.sh}"
BASE="${BASE:-/tmp/claude-wrapper-regression}"
FAKE_BIN="$BASE/bin"
mkdir -p "$FAKE_BIN" "$BASE/out"

cat > "$FAKE_BIN/claude" <<'FAKE'
#!/usr/bin/env bash
set -euo pipefail
prompt=""
model=""
fmt="text"
tools_seen=false
disallowed_tools=""
while [ $# -gt 0 ]; do
  case "$1" in
    -p|--print) prompt="${2:-}"; shift 2 ;;
    --model) model="${2:-}"; shift 2 ;;
    --output-format) fmt="${2:-}"; shift 2 ;;
    --tools) tools_seen=true; shift 2 ;;
    --disallowedTools) disallowed_tools="${2:-}"; shift 2 ;;
    *) shift ;;
  esac
done
[ -n "${FAKE_ARGS_FILE:-}" ] && printf '%s\n' "$disallowed_tools" > "$FAKE_ARGS_FILE"
if [ "$model" = "definitely-not-a-model" ]; then
  echo "model not found: $model" >&2
  exit 42
fi
if [ "$fmt" = "stream-json" ]; then
  # Emit a JSONL event log like `--output-format stream-json --verbose`: some
  # progress events, then the terminal result event on the final line.
  printf '%s\n' '{"type":"system","subtype":"init","session_id":"fake-stream"}'
  printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"working"}]}}'
  if [[ "$prompt" == *LONG_RESULT* ]]; then
    python3 - <<'PYLONG'
import json
print(json.dumps({
  "type": "result",
  "subtype": "success",
  "is_error": False,
  "terminal_reason": "completed",
  "stop_reason": "end_turn",
  "num_turns": 2,
  "total_cost_usd": 0.03,
  "session_id": "fake-stream",
  "result": "x" * 10000,
}))
PYLONG
  else
    printf '%s\n' '{"type":"result","subtype":"success","is_error":false,"terminal_reason":"completed","stop_reason":"end_turn","num_turns":2,"total_cost_usd":0.03,"session_id":"fake-stream","result":"streamed ok"}'
  fi
  exit 0
fi
if [ "$tools_seen" = true ]; then
  printf '%s\n' '{"type":"result","subtype":"success","is_error":false,"terminal_reason":"completed","stop_reason":"end_turn","num_turns":1,"total_cost_usd":0.02,"session_id":"fake-tools","result":"tools passed"}'
  exit 0
fi
if [[ "$prompt" == *CLAUDE_ERROR* ]]; then
  printf '%s\n' '{"type":"result","subtype":"error_max_turns","is_error":true,"terminal_reason":"max_turns","stop_reason":null,"num_turns":4,"total_cost_usd":0.01,"session_id":"fake-error","errors":["Reached maximum number of turns (4)"]}'
  exit 0
fi
printf '%s\n' '{"type":"result","subtype":"success","is_error":false,"terminal_reason":"completed","stop_reason":"end_turn","num_turns":1,"total_cost_usd":0.02,"session_id":"fake-success","result":"ok"}'
FAKE
chmod +x "$FAKE_BIN/claude"

export PATH="$FAKE_BIN:$PATH"
fail() { echo "FAIL: $*" >&2; exit 1; }

success_raw="$BASE/out/success.json"
"$WRAPPER" -P 'success case' -d /tmp -t 1 --format json -o "$success_raw" \
  > "$BASE/out/success.stdout" 2> "$BASE/out/success.stderr"
[ -s "$BASE/out/success.stdout" ] || fail "success stdout status envelope is empty"
jq --arg success_raw "$success_raw" -e '.status == "ok" and .subtype == "success" and .output_file == $success_raw and (.response_bytes > 0) and .num_turns == "1" and .total_cost_usd == "0.02" and .session_id == "fake-success"' "$BASE/out/success.stdout" >/dev/null \
  || fail "success stdout is not the expected status envelope"
jq -e '.subtype == "success"' "$success_raw" >/dev/null || fail "raw success artifact missing or invalid"

set +e
bad_raw="$BASE/out/bad-model.json"
"$WRAPPER" -P 'bad model case' -d /tmp -t 1 --format json -m definitely-not-a-model -o "$bad_raw" \
  > "$BASE/out/bad-model.stdout" 2> "$BASE/out/bad-model.stderr"
bad_exit=$?
set -e
[ "$bad_exit" -ne 0 ] || fail "bad-model wrapper exit was zero"
[ -s "$BASE/out/bad-model.stdout" ] || fail "bad-model stdout diagnostic envelope is empty"
jq --arg bad_raw "$bad_raw" -e '.status == "error" and .exit_code == 42 and .output_file == $bad_raw and .stderr_file and (.stderr_bytes > 0)' "$BASE/out/bad-model.stdout" >/dev/null \
  || fail "bad-model stdout is not the expected diagnostic envelope"

set +e
err_raw="$BASE/out/claude-error.json"
"$WRAPPER" -P 'CLAUDE_ERROR case' -d /tmp -t 1 --format json -o "$err_raw" \
  > "$BASE/out/claude-error.stdout" 2> "$BASE/out/claude-error.stderr"
err_exit=$?
set -e
[ "$err_exit" -ne 0 ] || fail "claude error subtype wrapper exit was zero"
jq -e '.status == "claude_error" and .subtype == "error_max_turns" and .is_error == true and .terminal_reason == "max_turns" and (.errors | length == 1)' "$BASE/out/claude-error.stdout" >/dev/null \
  || fail "claude error subtype was not reported as claude_error"

# --- streaming mode: JSONL artifact grows live; status derives from the final
# result event and exposes a bounded result_text -----------------------------
stream_raw="$BASE/out/stream.jsonl"
"$WRAPPER" -P 'stream case' -d /tmp --stream -o "$stream_raw" \
  > "$BASE/out/stream.stdout" 2> "$BASE/out/stream.stderr"
[ -s "$BASE/out/stream.stdout" ] || fail "stream stdout status envelope is empty"
jq --arg stream_raw "$stream_raw" -e '.status == "ok" and .subtype == "success" and .output_file == $stream_raw and .num_turns == "2" and .session_id == "fake-stream" and .result_text == "streamed ok"' "$BASE/out/stream.stdout" >/dev/null \
  || fail "stream stdout is not the expected status envelope"
# The artifact must be the multi-line JSONL event log, not a single blob.
[ "$(wc -l < "$stream_raw" | tr -d ' ')" -ge 3 ] || fail "stream artifact is not a multi-line JSONL event log"
jq -e '.type == "result"' <(tail -n1 "$stream_raw") >/dev/null || fail "stream artifact last line is not a result event"

long_stream_raw="$BASE/out/long-stream.jsonl"
"$WRAPPER" -P 'LONG_RESULT stream case' -d /tmp --stream -o "$long_stream_raw" \
  > "$BASE/out/long-stream.stdout" 2> "$BASE/out/long-stream.stderr"
jq -e '.status == "ok" and .subtype == "success" and (.result_text | length) == 4000' "$BASE/out/long-stream.stdout" >/dev/null \
  || fail "long stream result should be truncated without aborting status emission"

tools_raw="$BASE/out/tools.json"
"$WRAPPER" -P 'tools passthrough case' -d /tmp --format json --tools Read,Edit -o "$tools_raw" \
  > "$BASE/out/tools.stdout" 2> "$BASE/out/tools.stderr"
jq -e '.status == "ok" and .session_id == "fake-tools"' "$BASE/out/tools.stdout" >/dev/null \
  || fail "--tools passthrough did not reach claude"

# Nested agent tools are hard-denied by the runner even if the caller provides
# no prompt instruction or deny-list argument.
FAKE_ARGS_FILE="$BASE/out/disallowed-tools.txt" "$WRAPPER" -P 'hard deny check' -d /tmp --format json -o "$BASE/out/disallowed-tools.json" \
  > "$BASE/out/disallowed-tools.stdout" 2> "$BASE/out/disallowed-tools.stderr"
grep -q 'Agent' "$BASE/out/disallowed-tools.txt" || fail "Agent must be hard-denied"
grep -q 'Task' "$BASE/out/disallowed-tools.txt" || fail "Task must be hard-denied"

# --stream + --schema must be rejected (mutually exclusive)
set +e
"$WRAPPER" -P 'bad combo' -d /tmp --stream -s '{"type":"object"}' -o "$BASE/out/combo.jsonl" \
  > "$BASE/out/combo.stdout" 2> "$BASE/out/combo.stderr"
combo_exit=$?
set -e
[ "$combo_exit" -eq 1 ] || fail "--stream with --schema should exit 1, got $combo_exit"

# Force the portable Node watchdog even where GNU timeout is installed. The
# fake child never completes, so the wrapper must still terminate it promptly.
cat > "$FAKE_BIN/claude" <<'FAKE_SLEEP'
#!/usr/bin/env bash
sleep 30
FAKE_SLEEP
chmod +x "$FAKE_BIN/claude"
set +e
portable_started=$(date +%s)
CLAUDE_RUNNER_FORCE_PORTABLE_WATCHDOG=true "$WRAPPER" -P 'portable watchdog' -d /tmp --stream --timeout 1 -o "$BASE/out/portable-watchdog.jsonl" \
  > "$BASE/out/portable-watchdog.stdout" 2> "$BASE/out/portable-watchdog.stderr"
portable_exit=$?
portable_elapsed=$(( $(date +%s) - portable_started ))
set -e
[ "$portable_exit" -ne 0 ] || fail "portable watchdog should terminate a wedged claude"
[ "$portable_elapsed" -lt 5 ] || fail "portable watchdog took ${portable_elapsed}s (expected under 5s)"
jq -e '.status == "error" and .exit_code != 0' "$BASE/out/portable-watchdog.stdout" >/dev/null \
  || fail "portable watchdog did not emit a failure status envelope"

echo "regression checks passed"
