#!/usr/bin/env bash
# packaging/web-watchdog.test.sh — exercises both branches of the watchdog against a
# real HTTP server whose health is toggled by a flag file.
set -uo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
watchdog="${script_dir}/web-watchdog.sh"
work="$(mktemp -d)"
server_pid=""
failures=0

cleanup() {
  [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null
  rm -rf "$work"
}
trap cleanup EXIT

ok() { printf 'ok   %s\n' "$1"; }
no() { printf 'FAIL %s: %s\n' "$1" "$2" >&2; failures=$((failures + 1)); }

pages="${work}/pages"
mkdir -p "${pages}/plans" "${pages}/api"
touch "${pages}/index.astro" "${pages}/inbox.astro" "${pages}/plans/abandoned.astro" \
  "${pages}/plans/[runId].astro" "${pages}/api/health.astro" "${pages}/_hidden.astro"

port_file="${work}/port"
flag="${work}/unhealthy"
python3 - "$port_file" "$flag" <<'PY' >/dev/null 2>&1 &
import http.server, os, sys, threading
port_file, flag = sys.argv[1], sys.argv[2]

class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        # the torn-dist failure class: "/" keeps working, a lazily-loaded route 500s
        broken = os.path.exists(flag) and self.path != "/"
        self.send_response(500 if broken else 200)
        self.end_headers()
        self.wfile.write(b"x")
    def log_message(self, *a): pass

srv = http.server.HTTPServer(("127.0.0.1", 0), H)
with open(port_file, "w") as fh:
    fh.write(str(srv.server_address[1]))
srv.serve_forever()
PY
server_pid=$!

for _ in $(seq 1 50); do [[ -s "$port_file" ]] && break; sleep 0.1; done
[[ -s "$port_file" ]] || { echo "test harness: server never bound" >&2; exit 1; }
export OVERDECK_WEB_URL="http://127.0.0.1:$(<"$port_file")"
export OVERDECK_WEB_PAGES_DIR="$pages"

# --- routes: static pages only; dynamic, api/ and _-prefixed excluded
actual="$("$watchdog" routes | sort | tr '\n' ' ')"
expected="/ /inbox /plans/abandoned "
[[ "$actual" == "$expected" ]] && ok "routes lists static pages only" \
  || no "routes lists static pages only" "got '$actual' want '$expected'"

# --- healthy branch: sweep passes, daemon never restarts
"$watchdog" sweep >/dev/null 2>&1 && ok "sweep passes while healthy" \
  || no "sweep passes while healthy" "sweep exited non-zero"

restart_marker="${work}/restarted"
export OVERDECK_WEB_WATCHDOG_RESTART_CMD="touch '${restart_marker}'"
export OVERDECK_WEB_RELEASE_LOCK="$work/release.lock"
export OVERDECK_WEB_WATCHDOG_INTERVAL=2
export OVERDECK_WEB_WATCHDOG_FAILURES=2
export OVERDECK_WEB_WATCHDOG_SETTLE=1
export OVERDECK_WEB_WATCHDOG_PROBE_TIMEOUT=1 OVERDECK_WEB_WATCHDOG_MAX_HEALS=1

"$watchdog" daemon >/dev/null 2>&1 &
daemon_pid=$!
sleep 4
[[ -f "$restart_marker" ]] && no "healthy deck is not restarted" "watchdog restarted a healthy deck" \
  || ok "healthy deck is not restarted"

# --- unhealthy branch: "/" still 200s, one route 500s → restart
exec 9>"$OVERDECK_WEB_RELEASE_LOCK"
flock 9
touch "$flag"
sleep 5
if [[ -f "$restart_marker" ]]; then
  no "release transition lock inhibits watchdog restart" "watchdog restarted during a transition"
else
  ok "release transition lock inhibits watchdog restart"
fi
flock -u 9
for _ in $(seq 1 60); do [[ -f "$restart_marker" ]] && break; sleep 0.25; done
[[ -f "$restart_marker" ]] && ok "torn-route deck triggers a restart" \
  || no "torn-route deck triggers a restart" "no restart within 15s of the route going 500"
kill "$daemon_pid" 2>/dev/null
wait "$daemon_pid" 2>/dev/null

# --- persistent failure: second heal restores the previous release before restarting
rollback_marker="${work}/rolled-back"
rm -f "$restart_marker" "$rollback_marker"
export OVERDECK_WEB_WATCHDOG_RESTART_CMD="touch '${restart_marker}'"
export OVERDECK_WEB_WATCHDOG_ROLLBACK_CMD="touch '${rollback_marker}'"
export OVERDECK_WEB_WATCHDOG_FAILURES=1
export OVERDECK_WEB_WATCHDOG_MAX_HEALS=2
"$watchdog" daemon >/dev/null 2>&1 &
daemon_pid=$!
for _ in $(seq 1 40); do [[ -f "$rollback_marker" ]] && break; sleep 0.25; done
if [[ -f "$rollback_marker" ]]; then
  ok "persistent failure restores last known good release"
else
  no "persistent failure restores last known good release" "rollback did not run on the second heal"
fi
kill "$daemon_pid" 2>/dev/null
wait "$daemon_pid" 2>/dev/null

# --- fail closed: an unusable probe config must not report health
OVERDECK_WEB_PAGES_DIR="${work}/missing" "$watchdog" sweep >/dev/null 2>&1
[[ $? -eq 2 ]] && ok "missing pages dir fails closed" || no "missing pages dir fails closed" "did not exit 2"

# --- an unusable probe is not evidence the deck is broken: never restart on it
rm -f "$restart_marker"
OVERDECK_WEB_PAGES_DIR="${work}/missing" timeout 5 "$watchdog" daemon >/dev/null 2>&1
[[ -f "$restart_marker" ]] && no "unusable probe never restarts" "watchdog restarted on a broken probe" \
  || ok "unusable probe never restarts"

((failures == 0)) && { echo "web-watchdog: all tests passed"; exit 0; }
echo "web-watchdog: ${failures} test(s) failed" >&2
exit 1
