#!/usr/bin/env bash
# Regression test for prompt-optimiser-directive.sh.
#
# Covers:
#   1. Valid UserPromptSubmit stdin  -> exit 0, stdout is valid JSON,
#      hookEventName == "UserPromptSubmit", additionalContext non-empty.
#   2. Empty / garbage stdin         -> exit 0, still valid JSON (the hook
#      ignores the prompt text; the directive is standing).
#   3. python3 unavailable on PATH   -> exit 0, empty stdout (fail-open).

set -u

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

PASS=0
FAIL=0

# Assert: hook on $1 (stdin) exits 0 and stdout is JSON with a non-empty
# additionalContext and the correct hookEventName.
assert_valid_injection() {
  local name="$1" stdin="$2" out actual_exit
  out=$(printf '%s' "$stdin" | bash "$HOOK" 2>/dev/null)
  actual_exit=$?
  if [[ "$actual_exit" -ne 0 ]]; then
    echo "FAIL: $name (expected exit=0, got=$actual_exit)" >&2; FAIL=$((FAIL+1)); return
  fi
  if printf '%s' "$out" | python3 -c '
import json, sys
d = json.load(sys.stdin)
h = d["hookSpecificOutput"]
assert h["hookEventName"] == "UserPromptSubmit", "wrong hookEventName"
assert isinstance(h["additionalContext"], str) and len(h["additionalContext"]) > 0, "empty additionalContext"
' 2>/dev/null; then
    echo "PASS: $name"; PASS=$((PASS+1))
  else
    echo "FAIL: $name (stdout not valid directive JSON: $out)" >&2; FAIL=$((FAIL+1))
  fi
}

# Case 1 — valid UserPromptSubmit envelope.
assert_valid_injection "valid UserPromptSubmit stdin -> directive JSON" \
  '{"hook_event_name":"UserPromptSubmit","prompt":"fix the thing","session_id":"abc"}'

# Case 2 — garbage stdin still yields the standing directive.
assert_valid_injection "garbage stdin -> still directive JSON" \
  'not json at all'

# Case 3 — fail-open when python3 is absent from PATH.
FAKEBIN=$(mktemp -d)
# A minimal PATH containing the core utils the hook needs (cat, printf, env,
# command) but NOT python3. Resolve their real dir(s) and symlink them in.
for util in cat printf env bash; do
  src=$(command -v "$util" 2>/dev/null) && ln -sf "$src" "$FAKEBIN/$util" 2>/dev/null
done
nopy_out=$(printf '%s' '{"prompt":"x"}' | PATH="$FAKEBIN" bash "$HOOK" 2>/dev/null)
nopy_exit=$?
if [[ "$nopy_exit" -eq 0 && -z "$nopy_out" ]]; then
  echo "PASS: python3 absent -> exit 0, empty stdout (fail-open)"; PASS=$((PASS+1))
else
  echo "FAIL: python3 absent (expected exit=0 + empty stdout, got exit=$nopy_exit stdout=$nopy_out)" >&2; FAIL=$((FAIL+1))
fi
rm -rf "$FAKEBIN"

# Case 4 — both generated lists present: the ladder carries all four rungs, both
# full lists, and the relocated outcome-only-brief contract. The hook reads the
# lists from $PWD/agents/admin, so run it from a temp account dir.
WD=$(mktemp -d); mkdir -p "$WD/agents/admin"
printf -- '- **specialists:content-producer**: makes documents and published pages\n' > "$WD/agents/admin/AGENTS.md"
printf -- '- session-management: reset and list past sessions\n' > "$WD/agents/admin/ADMIN-SKILLS.md"
both=$(cd "$WD" && printf '%s' '{"prompt":"x"}' | bash "$HOOK" 2>/dev/null)
ctx=$(printf '%s' "$both" | python3 -c 'import json,sys; print(json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"])' 2>/dev/null)
if printf '%s' "$ctx" | grep -q "specialists:content-producer" \
   && printf '%s' "$ctx" | grep -q "session-management: reset and list past sessions" \
   && printf '%s' "$ctx" | grep -qE '(^|[^0-9])1\)' \
   && printf '%s' "$ctx" | grep -qE '(^|[^0-9])2\)' \
   && printf '%s' "$ctx" | grep -qE '(^|[^0-9])3\)' \
   && printf '%s' "$ctx" | grep -qE '(^|[^0-9])4\)' \
   && printf '%s' "$ctx" | grep -qiE "freestyle|last resort"; then
  echo "PASS: both lists injected in four-rung ladder"; PASS=$((PASS+1))
else
  echo "FAIL: ladder/lists missing from additionalContext: $ctx" >&2; FAIL=$((FAIL+1))
fi
# Task 1959: recurring-deliverable rung sits before the freestyle last resort.
if printf '%s' "$ctx" | grep -qi "skill-builder" \
   && printf '%s' "$ctx" | grep -qiE "recurs|issues repeatedly|repeatable" \
   && printf '%s' "$ctx" | grep -qi "agent-builder"; then
  echo "PASS: recurring-deliverable rung present in ladder"; PASS=$((PASS+1))
else
  echo "FAIL: recurring-deliverable rung missing from ladder: $ctx" >&2; FAIL=$((FAIL+1))
fi
# Relocated outcome-only-brief contract.
if printf '%s' "$ctx" | grep -qi "outcome" \
   && printf '%s' "$ctx" | grep -qi "binding constraints" \
   && printf '%s' "$ctx" | grep -qi "Wrong shape" \
   && printf '%s' "$ctx" | grep -qi "Right shape"; then
  echo "PASS: outcome-only-brief contract present in ladder"; PASS=$((PASS+1))
else
  echo "FAIL: outcome-only-brief contract missing from ladder" >&2; FAIL=$((FAIL+1))
fi
# Capability-question clause (Task 719): capability/how-to/config questions are
# owned work, answered from the owning specialist or plugin tool/reference,
# never from training memory.
if printf '%s' "$ctx" | grep -qi "owned work" \
   && printf '%s' "$ctx" | grep -qi "training memory" \
   && printf '%s' "$ctx" | grep -qi "do you have instructions"; then
  echo "PASS: capability-question clause present in directive"; PASS=$((PASS+1))
else
  echo "FAIL: capability-question clause missing from directive" >&2; FAIL=$((FAIL+1))
fi

# Case 5 — ADMIN-SKILLS.md absent: fail-open. exit 0, ladder still injected,
# and the missing= breadcrumb on stderr (fail-open must be visible).
rm -f "$WD/agents/admin/ADMIN-SKILLS.md"
miss_err=$(cd "$WD" && printf '%s' '{"prompt":"x"}' | bash "$HOOK" 2>&1 >/dev/null)
miss_out=$(cd "$WD" && printf '%s' '{"prompt":"x"}' | bash "$HOOK" 2>/dev/null); miss_exit=$?
if [[ "$miss_exit" -eq 0 ]] \
   && printf '%s' "$miss_err" | grep -q "missing=ADMIN-SKILLS.md" \
   && printf '%s' "$miss_out" | grep -q "hookSpecificOutput"; then
  echo "PASS: missing list -> exit 0 + ladder + visible missing= breadcrumb"; PASS=$((PASS+1))
else
  echo "FAIL: fail-open visibility wrong (exit=$miss_exit err=$miss_err)" >&2; FAIL=$((FAIL+1))
fi
rm -rf "$WD"

# Case 6 — durable breadcrumb (Task 719). With LOG_DIR set and a session_id on
# stdin, the hook appends one `injected len=` line carrying the session id to
# $LOG_DIR/prompt-optimiser-directive.log, in addition to the stderr line.
LD=$(mktemp -d)
printf '%s' '{"prompt":"x","session_id":"sess-719-test"}' | LOG_DIR="$LD" bash "$HOOK" >/dev/null 2>&1
if [[ -f "$LD/prompt-optimiser-directive.log" ]] \
   && grep -q "prompt-optimiser-directive] injected len=" "$LD/prompt-optimiser-directive.log" \
   && grep -q "session=sess-719-test" "$LD/prompt-optimiser-directive.log"; then
  echo "PASS: durable breadcrumb appended with session id"; PASS=$((PASS+1))
else
  echo "FAIL: durable breadcrumb missing (log=$(cat "$LD/prompt-optimiser-directive.log" 2>/dev/null))" >&2; FAIL=$((FAIL+1))
fi
rm -rf "$LD"

# Case 7 — Task 746: full directive persisted per turn. With LOG_DIR + a
# session id, the hook writes a content file whose bytes equal the emitted
# additionalContext, and the breadcrumb carries file= pointing at it plus a
# sha256 matching that file's bytes.
P746=$(mktemp -d)
out746=$(printf '%s' '{"prompt":"x","session_id":"sess-746-a"}' | LOG_DIR="$P746" bash "$HOOK" 2>/dev/null)
printf '%s' "$out746" | python3 -c 'import json,sys; sys.stdout.write(json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"])' > "$P746/expected.txt" 2>/dev/null
crumb="$P746/prompt-optimiser-directive.log"
dfile=$(grep -o 'file=[^ ]*' "$crumb" 2>/dev/null | head -1 | cut -d= -f2-)
slog=$(grep -o 'sha256=[0-9a-f]*' "$crumb" 2>/dev/null | head -1 | cut -d= -f2)
sfile=$(python3 -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$dfile" 2>/dev/null)
if [[ -f "$dfile" ]] && cmp -s "$dfile" "$P746/expected.txt" \
   && [[ -n "$slog" && "$slog" == "$sfile" ]] \
   && [[ "$dfile" == "$P746/prompt-optimiser-directives/sess-746-a/"*.txt ]]; then
  echo "PASS: full directive persisted; breadcrumb file=/sha256 match content"; PASS=$((PASS+1))
else
  echo "FAIL: directive content store wrong (file=$dfile slog=$slog sfile=$sfile)" >&2; FAIL=$((FAIL+1))
fi

# Case 8 — two injections in one session produce two distinct content files
# (one file per injection, so the audit's injected==stored invariant holds).
printf '%s' '{"prompt":"y","session_id":"sess-746-b"}' | LOG_DIR="$P746" bash "$HOOK" >/dev/null 2>&1
sleep 1
printf '%s' '{"prompt":"z","session_id":"sess-746-b"}' | LOG_DIR="$P746" bash "$HOOK" >/dev/null 2>&1
nfiles=$(ls "$P746/prompt-optimiser-directives/sess-746-b/"*.txt 2>/dev/null | wc -l | tr -d ' ')
if [[ "$nfiles" -eq 2 ]]; then
  echo "PASS: two injections -> two distinct content files"; PASS=$((PASS+1))
else
  echo "FAIL: expected 2 content files for sess-746-b, got $nfiles" >&2; FAIL=$((FAIL+1))
fi
rm -rf "$P746"

# Case 9 — Task 753: a native channel turn (the prompt is a `<channel source=`
# event) suppresses the directive — the event-text reframe replaces it there.
# Exit 0, EMPTY stdout (nothing injected), and a visible stderr breadcrumb.
chan_in='{"hook_event_name":"UserPromptSubmit","prompt":"<channel source=\"whatsapp\" sender_id=\"x\">\nprice the Willow House job\n</channel>","session_id":"chan-1"}'
chan_out=$(printf '%s' "$chan_in" | bash "$HOOK" 2>/dev/null); chan_exit=$?
chan_err=$(printf '%s' "$chan_in" | bash "$HOOK" 2>&1 >/dev/null)
if [[ "$chan_exit" -eq 0 && -z "$chan_out" ]] \
   && printf '%s' "$chan_err" | grep -q "skipped reason=channel-turn"; then
  echo "PASS: channel turn -> directive suppressed (exit 0, empty stdout, breadcrumb)"; PASS=$((PASS+1))
else
  echo "FAIL: channel-turn suppression wrong (exit=$chan_exit stdout=$chan_out err=$chan_err)" >&2; FAIL=$((FAIL+1))
fi

# Case 10 — Task 753: a normal admin turn that merely MENTIONS the word channel
# is NOT suppressed — only the `<channel source=` event marker suppresses, so a
# Terminal/admin prompt still gets the directive.
norm_out=$(printf '%s' '{"prompt":"please open a support channel for acme"}' | bash "$HOOK" 2>/dev/null); norm_exit=$?
if [[ "$norm_exit" -eq 0 ]] && printf '%s' "$norm_out" | grep -q "hookSpecificOutput"; then
  echo "PASS: non-channel prompt mentioning 'channel' still gets the directive"; PASS=$((PASS+1))
else
  echo "FAIL: false-suppression on a normal prompt (exit=$norm_exit stdout=$norm_out)" >&2; FAIL=$((FAIL+1))
fi

echo "----"
echo "PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]]
