#!/usr/bin/env bash
# Regression test for askuserquestion-channel-carrier-gate.sh.
#
# The hook blocks AskUserQuestion when the session has a native channel
# attached — signalled by a channel MCP config file for this session id in
# tmpdir. A channel carries no elicitation card, so the turn would wedge; the
# hook steers the agent to ask in prose via its channel reply tool instead.
#
# Covers (per .tasks/1552):
#   1. Channel attached (webchat config file present) + AskUserQuestion  → BLOCK channel-attached
#   2. No channel file + AskUserQuestion                                 → ALLOW no-channel
#   3. Non-AskUserQuestion tool                                          → exit 0 silent (no [ask-channel] line)
#   4. PostToolUse event on AskUserQuestion                             → exit 0 silent
#   5. Missing session_id                                               → ALLOW fail-open-no-session
#   6. No stdin envelope                                                → ALLOW fail-open-no-envelope
#   7. Each of the 4 channel filename shapes present                    → BLOCK channel-attached
#   8. Session id needing sanitization, sanitized file present          → BLOCK channel-attached
#
# All decisive cases assert exit code AND the decision-log signature on stderr.

set -u

HOOK="$(cd "$(dirname "$0")/.." && pwd)/askuserquestion-channel-carrier-gate.sh"
if [ ! -x "$HOOK" ]; then
  echo "FAIL: $HOOK not executable" >&2
  exit 1
fi

PASS=0
FAIL=0

# Run one case. Args:
#   $1 = name
#   $2 = stdin payload ("" for no-envelope: closed stdin)
#   $3 = tmpdir the hook should scan (ASK_CHANNEL_TMPDIR override), "" for default
#   $4 = expected exit code
#   $5 = expected log signature regex (matched against stderr; "" = assert stderr is empty)
run_case() {
  local name="$1" stdin="$2" scan_dir="$3" expected_exit="$4" expected_log_re="$5"
  local stderr_file actual_exit
  stderr_file="$(mktemp)"
  if [ -z "$stdin" ]; then
    # No envelope: close stdin so the hook's `[ -t 0 ]` / empty-read path fires.
    ASK_CHANNEL_TMPDIR="$scan_dir" bash "$HOOK" >/dev/null 2>"$stderr_file" </dev/null
  else
    printf '%s' "$stdin" | ASK_CHANNEL_TMPDIR="$scan_dir" bash "$HOOK" >/dev/null 2>"$stderr_file"
  fi
  actual_exit=$?
  local stderr_content
  stderr_content=$(cat "$stderr_file")
  rm -f "$stderr_file"

  local exit_ok=0 log_ok=0
  if [ "$actual_exit" -eq "$expected_exit" ]; then exit_ok=1; fi
  if [ -z "$expected_log_re" ]; then
    # Empty regex means: assert the hook emitted NO [ask-channel] line.
    if ! printf '%s' "$stderr_content" | grep -q '\[ask-channel\]'; then log_ok=1; fi
  else
    if printf '%s' "$stderr_content" | grep -Eq "$expected_log_re"; then log_ok=1; fi
  fi

  if [ $exit_ok -eq 1 ] && [ $log_ok -eq 1 ]; then
    echo "PASS: $name (exit=$actual_exit)"
    PASS=$((PASS + 1))
  else
    echo "FAIL: $name" >&2
    [ $exit_ok -eq 0 ] && echo "  expected exit=$expected_exit, got=$actual_exit" >&2
    [ $log_ok -eq 0 ]  && echo "  expected log regex: ${expected_log_re:-<none>}" >&2 && \
                          echo "  actual stderr: $stderr_content" >&2
    FAIL=$((FAIL + 1))
  fi
}

SID="1a085718-1111-2222-3333-444455556666"
SID8="1a085718"

# Stdin payloads.
PRE_ASK="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$SID\",\"tool_input\":{\"questions\":[]}}"
PRE_OTHER="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Bash\",\"session_id\":\"$SID\",\"tool_input\":{\"command\":\"ls\"}}"
POST_ASK="{\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$SID\",\"tool_input\":{\"questions\":[]}}"
PRE_ASK_NO_SID='{"hook_event_name":"PreToolUse","tool_name":"AskUserQuestion","tool_input":{"questions":[]}}'

# ---------- Cases ----------

# 1. Channel attached (webchat config present) → BLOCK.
DIR_WEBCHAT=$(mktemp -d)
: > "$DIR_WEBCHAT/maxy-webchat-channel-$SID.json"
run_case "Webchat channel attached → BLOCK channel-attached" \
  "$PRE_ASK" "$DIR_WEBCHAT" 2 \
  "\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached"
rm -rf "$DIR_WEBCHAT"

# 2. No channel file → ALLOW no-channel.
DIR_EMPTY=$(mktemp -d)
run_case "No channel file → ALLOW no-channel" \
  "$PRE_ASK" "$DIR_EMPTY" 0 \
  "\[ask-channel\] decision=allow sessionId=$SID8 channel=none reason=no-channel"
rm -rf "$DIR_EMPTY"

# 3. Non-AskUserQuestion tool → silent exit 0.
DIR_WITH_FILE=$(mktemp -d)
: > "$DIR_WITH_FILE/maxy-webchat-channel-$SID.json"
run_case "Non-AskUserQuestion tool → exit 0 silent" \
  "$PRE_OTHER" "$DIR_WITH_FILE" 0 ""
rm -rf "$DIR_WITH_FILE"

# 4. PostToolUse event → silent exit 0 (even with channel file present).
DIR_POST=$(mktemp -d)
: > "$DIR_POST/maxy-webchat-channel-$SID.json"
run_case "PostToolUse on AskUserQuestion → exit 0 silent" \
  "$POST_ASK" "$DIR_POST" 0 ""
rm -rf "$DIR_POST"

# 5. Missing session_id → ALLOW fail-open-no-session.
DIR_NOSID=$(mktemp -d)
run_case "Missing session_id → ALLOW fail-open-no-session" \
  "$PRE_ASK_NO_SID" "$DIR_NOSID" 0 \
  '\[ask-channel\] decision=allow sessionId=- channel=none reason=fail-open-no-session'
rm -rf "$DIR_NOSID"

# 6. No stdin envelope → ALLOW fail-open-no-envelope.
run_case "No stdin envelope → ALLOW fail-open-no-envelope" \
  "" "" 0 \
  '\[ask-channel\] decision=allow sessionId=- channel=none reason=fail-open-no-envelope'

# 7. Each of the 4 channel filename shapes present → BLOCK.
for kind in channel webchat-channel wa-channel telegram-channel; do
  DIR_KIND=$(mktemp -d)
  : > "$DIR_KIND/maxy-$kind-$SID.json"
  run_case "Channel file shape maxy-$kind-<sid>.json → BLOCK channel-attached" \
    "$PRE_ASK" "$DIR_KIND" 2 \
    "\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached"
  rm -rf "$DIR_KIND"
done

# 8. Session id needing sanitization: raw id has chars outside [A-Za-z0-9_-];
#    the writer sanitizes to '_', so the on-disk file uses the sanitized id.
RAW_SID="acc/one:sess"          # '/' and ':' are sanitized to '_'
SAN_SID="acc_one_sess"
DIR_SAN=$(mktemp -d)
: > "$DIR_SAN/maxy-webchat-channel-$SAN_SID.json"
PRE_ASK_RAW="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$RAW_SID\",\"tool_input\":{\"questions\":[]}}"
run_case "Sanitized session id resolves the file → BLOCK channel-attached" \
  "$PRE_ASK_RAW" "$DIR_SAN" 2 \
  '\[ask-channel\] decision=block sessionId=acc/one: channel=attached reason=channel-attached'
rm -rf "$DIR_SAN"

# 9. Real tmpdir resolution fallback: no ASK_CHANNEL_TMPDIR override, TMPDIR
#    unset, TMP set → the hook must resolve TMP (mirroring Node's os.tmpdir())
#    and find the config file there. Proves the production resolution order, not
#    just the test override.
DIR_TMP=$(mktemp -d)
: > "$DIR_TMP/maxy-webchat-channel-$SID.json"
tmp_stderr="$(mktemp)"
printf '%s' "$PRE_ASK" | env -u TMPDIR -u ASK_CHANNEL_TMPDIR -u TEMP TMP="$DIR_TMP" \
  bash "$HOOK" >/dev/null 2>"$tmp_stderr"
tmp_exit=$?
if [ "$tmp_exit" -eq 2 ] && grep -Eq "\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached" "$tmp_stderr"; then
  echo "PASS: TMP-fallback resolution finds the config file → BLOCK (exit=$tmp_exit)"
  PASS=$((PASS + 1))
else
  echo "FAIL: TMP-fallback resolution" >&2
  echo "  expected exit=2 got=$tmp_exit; stderr: $(cat "$tmp_stderr")" >&2
  FAIL=$((FAIL + 1))
fi
rm -f "$tmp_stderr"
rm -rf "$DIR_TMP"

echo
echo "──────── askuserquestion-channel-carrier-gate test summary ────────"
echo "PASS: $PASS"
echo "FAIL: $FAIL"

[ "$FAIL" -gt 0 ] && exit 1
exit 0
