#!/usr/bin/env bash
# settings-heal: restoring a gutted settings.json, and refusing to make a partial loss
# canonical. A writer that drops only the safety hooks leaves `hooks` non-empty, so a
# structural check alone promotes the loss into known-good and the hooks never come back.
set -uo pipefail

REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)
HEAL="$REPO_ROOT/modules/workstation/claude/hooks/settings-heal.sh"
PASS=0
FAIL=0

ok() {
  PASS=$((PASS + 1))
  printf 'ok   %s\n' "$1"
}
no() {
  FAIL=$((FAIL + 1))
  printf 'FAIL %s\n' "$1"
}
check() { if [[ $2 == "$3" ]]; then ok "$1"; else no "$1 (want '$3', got '$2')"; fi; }

TESTROOT=$(mktemp -d -t settings-heal-test.XXXXXX)
trap 'rm -rf "$TESTROOT"' EXIT

SETTINGS="$TESTROOT/settings.json"
KNOWN_GOOD="$TESTROOT/settings.json.known-good"
LOG="$TESTROOT/heal.log"
printf '#!/usr/bin/env bash\n' >"$TESTROOT/statusline.sh"

heal() {
  CLAUDE_SETTINGS="$SETTINGS" CLAUDE_SETTINGS_KNOWN_GOOD="$KNOWN_GOOD" \
    CLAUDE_SETTINGS_HEAL_LOG="$LOG" bash "$HEAL"
}

# The three hooks landed today that a partial write must never be able to drop silently.
LEDGER='node /home/user/.claude/hooks/agent-session-ledger.mjs'
KILLGUARD='node /home/user/.claude/hooks/human-kill-guard.mjs'

full_settings() {
  cat <<JSON
{
  "model": "opus",
  "statusLine": {"type": "command", "command": "bash $TESTROOT/statusline.sh render"},
  "hooks": {
    "SessionStart": [{"hooks": [{"type": "command", "command": "$LEDGER"}]}],
    "SessionEnd": [{"hooks": [{"type": "command", "command": "$LEDGER"}]}],
    "PreToolUse": [
      {"matcher": "Bash", "hooks": [{"type": "command", "command": "$KILLGUARD"}]},
      {"matcher": "Bash", "hooks": [{"type": "command", "command": "other-hook.sh"}]}
    ]
  }
}
JSON
}

# Hooks survive, only the session-ledger and kill-guard entries are gone.
partial_settings() {
  cat <<JSON
{
  "model": "opus",
  "statusLine": {"type": "command", "command": "bash $TESTROOT/statusline.sh render"},
  "hooks": {
    "PreToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "other-hook.sh"}]}]
  }
}
JSON
}

# --- 1. bootstrap: a healthy settings.json with no known-good yet becomes the known-good -
full_settings >"$SETTINGS"
heal
[[ -s $KNOWN_GOOD ]] && ok "first healthy settings.json seeds known-good" ||
  no "first healthy settings.json seeds known-good"

# --- 2. a write that drops only the safety hooks is never promoted ----------------------
partial_settings >"$SETTINGS"
heal
if grep -q "$LEDGER" "$KNOWN_GOOD" && grep -q "$KILLGUARD" "$KNOWN_GOOD"; then
  ok "known-good keeps the hooks a partial write dropped"
else
  no "known-good keeps the hooks a partial write dropped"
fi
grep -q "REFUSED to promote" "$LOG" && ok "the refusal is recorded, not silent" ||
  no "the refusal is recorded, not silent"

# A deliberate removal must survive: the healer refuses to promote, never to revert.
grep -q "$LEDGER" "$SETTINGS" &&
  no "settings.json is left as its author wrote it" ||
  ok "settings.json is left as its author wrote it"

# --- 3. a write that drops the statusline is never promoted -------------------------------
full_settings >"$SETTINGS"
heal
cp "$SETTINGS" "$KNOWN_GOOD"
python3 - "$SETTINGS" <<'PY'
import json
import sys

path = sys.argv[1]
with open(path, encoding="utf-8") as handle:
    payload = json.load(handle)
payload.pop("statusLine")
with open(path, "w", encoding="utf-8") as handle:
    json.dump(payload, handle)
PY
heal
grep -q 'statusLine' "$SETTINGS" && ok "a dropped statusline is restored" ||
  no "a dropped statusline is restored"
grep -q 'RESTORED settings.json' "$LOG" && ok "statusline restoration is recorded" ||
  no "statusline restoration is recorded"

# --- 4. the named escape hatch actually works -------------------------------------------
cp "$SETTINGS" "$KNOWN_GOOD"
: >"$LOG"
heal
grep -q "REFUSED to promote" "$LOG" &&
  no "confirming the removal in known-good clears the refusal" ||
  ok "confirming the removal in known-good clears the refusal"

# --- 4. a gutted settings.json is still restored -----------------------------------------
full_settings >"$SETTINGS"
heal
printf '{"model": "opus"}\n' >"$SETTINGS"
heal
grep -q "$LEDGER" "$SETTINGS" && ok "a gutted settings.json is restored from known-good" ||
  no "a gutted settings.json is restored from known-good"
grep -q "RESTORED settings.json" "$LOG" && ok "the restore is recorded" || no "the restore is recorded"

# --- 5. an unhealthy known-good is never used to overwrite a live settings.json ----------
printf '{"model": "opus"}\n' >"$KNOWN_GOOD"
printf 'not json at all\n' >"$SETTINGS"
heal
check "settings.json is untouched when known-good is itself unusable" \
  "$(cat "$SETTINGS")" "not json at all"

# --- 6. shapes that keep `hooks` non-empty while running nothing ------------------------
# The old check was `hooks is a non-empty dict`, which every one of these passes.
full_settings >"$SETTINGS"
heal
cp "$SETTINGS" "$KNOWN_GOOD"

broken_is_restored() {
  printf '%s\n' "$2" >"$SETTINGS"
  heal
  grep -q "$LEDGER" "$SETTINGS" && ok "$1" || no "$1"
}

broken_is_restored "an event with no hook groups is unhealthy, not merely empty" \
  '{"hooks": {"SessionStart": []}}'
broken_is_restored "a group whose hooks list is empty is unhealthy" \
  '{"hooks": {"SessionStart": [{"hooks": []}]}}'
broken_is_restored "a hook with no command is unhealthy" \
  '{"hooks": {"SessionStart": [{"hooks": [{"type": "command"}]}]}}'
broken_is_restored "a hook whose command is blank is unhealthy" \
  '{"hooks": {"SessionStart": [{"hooks": [{"type": "command", "command": "   "}]}]}}'
broken_is_restored "a hook pointing at a script that no longer exists is unhealthy" \
  '{"hooks": {"SessionStart": [{"hooks": [{"type": "command", "command": "node /nonexistent/gone.mjs"}]}]}}'

printf '\n%s passed, %s failed\n' "$PASS" "$FAIL"
[[ $FAIL -eq 0 ]]
