#!/usr/bin/env bash
# shellcheck disable=SC2319,SC2329
# Typed response and fake-monotonic deadline coverage for deploy readiness.
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
READINESS_LIB="$ROOT/packaging/lib/readiness.sh"
PROBE_SCRIPT="$ROOT/packaging/deploy-readiness.sh"
TESTROOT="${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/typed-readiness/run-$$-$RANDOM"
mkdir -p "$TESTROOT"
server_pid=""
cleanup() { [[ -z "$server_pid" ]] || kill "$server_pid" 2>/dev/null || true; rm -rf "$TESTROOT"; }
trap cleanup EXIT
fails=0
check() { if (( $1 == 0 )); then printf 'ok - %s\n' "$2"; else printf 'FAIL - %s\n' "$2"; fails=$((fails + 1)); fi; }

# shellcheck disable=SC1090,SC1091
source "$READINESS_LIB"
clock_file="$TESTROOT/clock"
sleep_log="$TESTROOT/sleeps"
probe_count_file="$TESTROOT/probes"
printf '1000\n' >"$clock_file"
: >"$sleep_log"
monotonic_milliseconds() { printf '%s\n' "$(<"$clock_file")"; }
readiness_sleep() {
  local ms=$1 now
  now=$(<"$clock_file")
  printf '%s\n' "$((now + ms))" >"$clock_file"
  printf '%s\n' "$ms" >>"$sleep_log"
}
probe_after() {
  local wanted=$1 count=0
  [[ ! -f "$probe_count_file" ]] || count=$(<"$probe_count_file")
  count=$((count + 1)); printf '%s\n' "$count" >"$probe_count_file"
  (( count >= wanted ))
}

rm -f "$probe_count_file"; printf '1000\n' >"$clock_file"; : >"$sleep_log"
wait_for_readiness 5 1 probe_after 1
condition_rc=$?
check "$condition_rc" 'probe runs immediately and can succeed without sleeping'
[[ "$READINESS_ATTEMPTS" -eq 1 && "$READINESS_ELAPSED_MS" -eq 0 && ! -s "$sleep_log" ]]
condition_rc=$?
check "$condition_rc" 'immediate success records zero monotonic elapsed time'

rm -f "$probe_count_file"; printf '1000\n' >"$clock_file"; : >"$sleep_log"
wait_for_readiness 5 1 probe_after 3
condition_rc=$?
check "$condition_rc" 'transient typed failures retry to success'
[[ "$READINESS_ATTEMPTS" -eq 3 && "$READINESS_ELAPSED_MS" -eq 2000 ]]
condition_rc=$?
check "$condition_rc" 'retry evidence uses injected monotonic time'

rm -f "$probe_count_file"; printf '1000\n' >"$clock_file"; : >"$sleep_log"
wait_for_readiness 3 1 probe_after 99; deadline_rc=$?
[[ "$deadline_rc" -eq 1 && "$READINESS_ATTEMPTS" -eq 3 && "$READINESS_ELAPSED_MS" -eq 3000 ]]
condition_rc=$?
check "$condition_rc" 'failure stops exactly at the monotonic deadline without a deadline probe'

rm -f "$probe_count_file"; printf '1000\n' >"$clock_file"; : >"$sleep_log"
wait_for_readiness 5 3 probe_after 99; cap_rc=$?
[[ "$cap_rc" -eq 1 && "$(paste -sd, "$sleep_log")" == 3000,2000 ]]
condition_rc=$?
check "$condition_rc" 'last sleep is capped to remaining deadline'
! grep -q 'date +%s' "$READINESS_LIB"
condition_rc=$?
check "$condition_rc" 'deadline helper never reads wall clock'

cat >"$TESTROOT/server.py" <<'PY'
import json, os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
counts = {}
token = os.environ["TEST_TOKEN"]
class Handler(BaseHTTPRequestHandler):
    def log_message(self, *_): pass
    def do_GET(self):
        counts[self.path] = counts.get(self.path, 0) + 1
        delayed = self.path.startswith("/delayed-") and counts[self.path] < 3
        path = "/" + self.path.removeprefix("/delayed-") if self.path.startswith("/delayed-") else self.path
        if delayed or path == "/bad-status":
            self.send_response(503); self.end_headers(); self.wfile.write(b'{"ok":false}'); return
        if path in ("/collector", "/controller"):
            if self.headers.get("Authorization") != f"Bearer {token}":
                self.send_response(401); self.end_headers(); return
            body = {"ok": True}
        elif path.startswith("/bots"):
            body = [{"id": "fixture"}]
        elif path == "/wrong-type":
            body = {"ok": True}
        elif path == "/bad-json":
            self.send_response(200); self.end_headers(); self.wfile.write(b'not-json'); return
        else:
            self.send_response(404); self.end_headers(); return
        payload = json.dumps(body, separators=(",", ":")).encode()
        self.send_response(200); self.send_header("content-type", "application/json"); self.end_headers(); self.wfile.write(payload)
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
Path(os.environ["PORT_FILE"]).write_text(str(server.server_port))
server.serve_forever()
PY
secret='typed-readiness-secret-never-print'
printf '%s' "$secret" >"$TESTROOT/token"
TEST_TOKEN="$secret" PORT_FILE="$TESTROOT/port" python3 "$TESTROOT/server.py" &
server_pid=$!
for _ in $(seq 1 100); do [[ -s "$TESTROOT/port" ]] && break; sleep 0.02; done
port=$(<"$TESTROOT/port")
cat >"$TESTROOT/web-watchdog" <<'SH'
#!/usr/bin/env bash
count=0
[[ ! -f "$WEB_COUNT" ]] || count=$(<"$WEB_COUNT")
count=$((count + 1)); printf '%s\n' "$count" >"$WEB_COUNT"
(( count >= ${WEB_READY_AFTER:-1} ))
SH
chmod +x "$TESTROOT/web-watchdog"

run_typed() {
  OVERDECK_READINESS_TOKEN_FILE="$TESTROOT/token" \
  OVERDECK_READINESS_COLLECTOR_URL="http://127.0.0.1:$port/${1:-collector}" \
  OVERDECK_READINESS_CONTROLLER_URL="http://127.0.0.1:$port/${2:-controller}" \
  OVERDECK_READINESS_BOTMASTER_URL="http://127.0.0.1:$port/${3:-bots}" \
  OVERDECK_READINESS_WEB_WATCHDOG="$TESTROOT/web-watchdog" \
  OVERDECK_READINESS_CURL_TIMEOUT=2 WEB_COUNT="$TESTROOT/web-count" \
  WEB_READY_AFTER="${WEB_READY_AFTER:-1}" bash "$PROBE_SCRIPT"
}

rm -f "$TESTROOT/web-count"
typed_output=$(run_typed collector controller bots 2>&1); typed_rc=$?
[[ "$typed_rc" -eq 0 && "$typed_output" == *'collector=ready controller=ready botmaster=ready web=ready'* ]]
condition_rc=$?
check "$condition_rc" 'one pass accepts exact authenticated health objects, bot array, and web sweep'
[[ "$typed_output" != *"$secret"* ]]
condition_rc=$?
check "$condition_rc" 'readiness output never exposes bearer token'

rm -f "$TESTROOT/web-count"
wrong_output=$(run_typed collector controller wrong-type 2>&1); wrong_rc=$?
[[ "$wrong_rc" -ne 0 && "$wrong_output" == *'botmaster=wrong-json-type'* ]]
condition_rc=$?
check "$condition_rc" 'HTTP 200 with the wrong JSON type is rejected'

rm -f "$TESTROOT/web-count"
bad_output=$(run_typed bad-status controller bots 2>&1); bad_rc=$?
[[ "$bad_rc" -ne 0 && "$bad_output" == *'collector=http-503'* ]]
condition_rc=$?
check "$condition_rc" 'non-200 service response is rejected by name'

# Real processes + real monotonic time: all four probes become typed-ready on the third pass.
rm -f "$TESTROOT/web-count"
WEB_READY_AFTER=3
real_probe() { run_typed delayed-collector delayed-controller delayed-bots >/dev/null 2>&1; }
# Restore production clock/sleep definitions after the fake-clock cases.
unset -f monotonic_milliseconds readiness_sleep
# shellcheck disable=SC1090,SC1091
source "$READINESS_LIB"
wait_for_readiness 5 1 real_probe
real_rc=$?
[[ "$real_rc" -eq 0 && "$READINESS_ATTEMPTS" -eq 3 && "$READINESS_ELAPSED_MS" -ge 1900 && "$READINESS_ELAPSED_MS" -lt 5000 ]]
condition_rc=$?
check "$condition_rc" 'real delayed services converge under one monotonic deadline'

(( fails == 0 )) || { printf '%s check(s) failed\n' "$fails" >&2; exit 1; }
printf 'typed-readiness: all checks passed\n'
