#!/usr/bin/env bash
# Regression test for the Bash-channel schema hooks:
#   fs-schema-guard-bash-pre.sh  (PreToolUse snapshot)
#   fs-schema-guard-bash-post.sh (PostToolUse diff + block)
#
# Covers:
#   1. Non-Bash tool (pre + post)                          -> ALLOW (early exit)
#   2. Pre takes a snapshot of the current top-level set   -> snapshot file exists
#   3. mkdir <root>/whim then post                         -> BLOCK top-level + log
#   4. mkdir <root>/output/x (allowed top-level) then post -> ALLOW
#   5. mkdir inside an existing allowed dir then post      -> ALLOW
#   6. Bash creating no new top-level entry then post      -> ALLOW (silent)
#   7. Pre-existing stray present before the command       -> NOT re-flagged
#   8. Post with allowed set empty (no SCHEMA.md)          -> ALLOW (fail open)
#   9. Post with pre-snapshot missing                      -> ALLOW (fail open)
set -u

HOOKS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PRE="$HOOKS_DIR/fs-schema-guard-bash-pre.sh"
POST="$HOOKS_DIR/fs-schema-guard-bash-post.sh"
[ -x "$PRE" ] || { echo "FAIL: $PRE not executable" >&2; exit 1; }
[ -x "$POST" ] || { echo "FAIL: $POST not executable" >&2; exit 1; }

REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
TEMPLATE="$REPO_ROOT/templates/account-schema/SCHEMA.md"
[ -f "$TEMPLATE" ] || { echo "FAIL: template missing at $TEMPLATE" >&2; exit 1; }

# Isolated tmp for snapshots so the run never collides with a live session.
SNAP_TMP=$(mktemp -d)
export FS_GUARD_BASH_TMPDIR="$SNAP_TMP"
trap 'rm -rf "$SNAP_TMP"' EXIT

PASS=0; FAIL=0
SID="test-session-1877"

# Build a stdin envelope for a given tool + session id.
env_json() { printf '{"hook_event_name":"%s","tool_name":"%s","session_id":"%s","tool_input":{}}' "$1" "$2" "$3"; }

# Fresh seeded account dir.
new_acct() {
  local d; d=$(mktemp -d)
  cp "$TEMPLATE" "$d/SCHEMA.md"
  mkdir -p "$d/projects/Acme" "$d/documents" "$d/output"
  printf '%s' "$d"
}

check() { # name expected_exit actual_exit expected_log_re actual_err
  local name="$1" exp="$2" act="$3" re="$4" err="$5" ok=1
  [ "$act" -eq "$exp" ] || ok=0
  if [ -n "$re" ] && ! printf '%s' "$err" | grep -Eq "$re"; then ok=0; fi
  if [ $ok -eq 1 ]; then echo "PASS: $name (exit=$act)"; PASS=$((PASS+1));
  else echo "FAIL: $name (exit=$act want $exp; log want /$re/; got: $err)" >&2; FAIL=$((FAIL+1)); fi
}

run_pre()  { ( cd "$1" && printf '%s' "$2" | bash "$PRE"  >/dev/null 2>"$3" ); return $?; }
run_post() { ( cd "$1" && printf '%s' "$2" | bash "$POST" >/dev/null 2>"$3" ); return $?; }

# --- 1. Non-Bash tool: both hooks early-exit allow -------------------------
A=$(new_acct)
ef=$(mktemp)
run_pre "$A" "$(env_json PreToolUse Write "$SID")" "$ef"; check "pre non-bash allow" 0 $? "" "$(cat "$ef")"
run_post "$A" "$(env_json PostToolUse Write "$SID")" "$ef"; check "post non-bash allow" 0 $? "" "$(cat "$ef")"

# --- 2. Pre snapshot is written -------------------------------------------
run_pre "$A" "$(env_json PreToolUse Bash "$SID")" "$ef"; rc=$?
SNAP="$SNAP_TMP/maxy-fsguard-snapshot-${SID}.txt"
if [ $rc -eq 0 ] && [ -f "$SNAP" ] && grep -qx "SCHEMA.md" "$SNAP"; then
  echo "PASS: pre writes snapshot (exit=$rc)"; PASS=$((PASS+1));
else echo "FAIL: pre snapshot missing/incomplete (exit=$rc, snap=$SNAP)" >&2; FAIL=$((FAIL+1)); fi

# --- 3. mkdir whim -> post blocks top-level with log -----------------------
# Snapshot from step 2 is the "before". Create the stray, then run post.
mkdir "$A/whim"
run_post "$A" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post blocks whim" 2 $? "\[fs-guard-bash\] stray path=whim reason=top-level" "$(cat "$ef")"

# --- 4. allowed top-level dir created via Bash -> no block ------------------
B=$(new_acct)
run_pre "$B" "$(env_json PreToolUse Bash "$SID")" "$ef"
# `output` already exists in new_acct, so create a different allowed one: uploads
mkdir "$B/uploads"
run_post "$B" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post allows allowed top-level" 0 $? "" "$(cat "$ef")"

# --- 5. mkdir inside an existing allowed dir -> no new top-level, no block --
C=$(new_acct)
run_pre "$C" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir -p "$C/projects/NewCo"
run_post "$C" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post allows nested-in-allowed" 0 $? "" "$(cat "$ef")"

# --- 6. Bash creating no new top-level entry -> silent allow ---------------
D=$(new_acct)
run_pre "$D" "$(env_json PreToolUse Bash "$SID")" "$ef"
# no fs change
run_post "$D" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post silent no-op" 0 $? "" "$(cat "$ef")"

# --- 7. Pre-existing stray is NOT re-flagged -------------------------------
E=$(new_acct)
mkdir "$E/_attic"            # legacy stray present BEFORE the command
run_pre "$E" "$(env_json PreToolUse Bash "$SID")" "$ef"   # snapshot includes _attic
mkdir "$E/fresh_whim"        # a new stray this command creates
run_post "$E" "$(env_json PostToolUse Bash "$SID")" "$ef"; rc=$?
err="$(cat "$ef")"
if [ $rc -eq 2 ] && printf '%s' "$err" | grep -q "path=fresh_whim" && ! printf '%s' "$err" | grep -q "path=_attic"; then
  echo "PASS: pre-existing stray not re-flagged (only fresh_whim)"; PASS=$((PASS+1));
else echo "FAIL: legacy backlog re-flagged (exit=$rc): $err" >&2; FAIL=$((FAIL+1)); fi

# --- 8. Empty allowed set (no SCHEMA.md) -> fail open ----------------------
F=$(mktemp -d); trap 'rm -rf "$SNAP_TMP" "$F"' EXIT
run_pre "$F" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir "$F/anything"
run_post "$F" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post fail-open no-schema" 0 $? "" "$(cat "$ef")"

# --- 9. Missing pre-snapshot -> fail open ----------------------------------
G=$(new_acct)
rm -f "$SNAP_TMP/maxy-fsguard-snapshot-missing_sid.txt"
mkdir "$G/whimtoo"
run_post "$G" "$(env_json PostToolUse Bash "missing sid")" "$ef"
check "post fail-open no-snapshot" 0 $? "" "$(cat "$ef")"

# --- 10. bad-named child of a bucket created THIS command -> block bad-name --
# contacts/ is absent from new_acct, so it is new this command; all its children
# are therefore this command's doing and a bad-named one is soundly flagged.
H=$(new_acct)
run_pre "$H" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir -p "$H/contacts/Bad Name"
run_post "$H" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post blocks bad-name in new bucket" 2 $? "\[fs-guard-bash\] stray path=Bad Name reason=bad-name" "$(cat "$ef")"

# --- 11. conforming child of a new bucket -> no block -----------------------
I=$(new_acct)
run_pre "$I" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir -p "$I/contacts/acme-corp"
run_post "$I" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post allows conforming new-bucket child" 0 $? "" "$(cat "$ef")"

# --- 12. bad name under a PRE-EXISTING bucket -> not flagged here ------------
# projects/ is in the pre-snapshot, so bash-post cannot tell a new child from the
# legacy backlog; flagging every one would re-spam the backlog. The reconcile is
# the net for this case. No block.
J=$(new_acct)
run_pre "$J" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir -p "$J/projects/Bad Name"
run_post "$J" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post ignores bad-name in pre-existing bucket" 0 $? "" "$(cat "$ef")"

# --- 13. dot-prefixed child of a new bucket -> not flagged (system-wide skip) --
K=$(new_acct)
run_pre "$K" "$(env_json PreToolUse Bash "$SID")" "$ef"
mkdir -p "$K/contacts/acme"; : > "$K/contacts/.DS_Store"
run_post "$K" "$(env_json PostToolUse Bash "$SID")" "$ef"
check "post ignores dot-prefixed new-bucket child" 0 $? "" "$(cat "$ef")"

rm -f "$ef"
echo "----- $PASS passed, $FAIL failed -----"
[ $FAIL -eq 0 ]
