#!/bin/bash
# bootstrap-modes.sh — R-21 slice C regression harness for felhom-bootstrap.sh's two modes. Runs as
# root inside a throwaway container (felhom-iso-assistant:trixie — python3 needed by the pairing
# parsers; writes /etc/felhom etc.); fakes curl + host-install + systemctl + sleep on PATH. Asserts:
#   D (regression): a DIRECT env (customer-id + passphrase) enters run_direct and makes ZERO calls to
#                   /api/v1/appliance/* — the pairing code path is provably not entered.
#   P (pairing+delivery, v1.21.0 loop semantics): an env WITHOUT customer-id/passphrase enters
#                   run_pairing, POSTs /appliance/register, persists the token, polls 204 INSIDE one
#                   invocation (fake sleep counts the waits; the script never exits between polls),
#                   then consumes the 200 delivery, writes the direct env and runs host-install.
#   410:            a consumed-delivery poll exits non-zero (the crash-window hand-back to systemd).
#   G1 (in D+P):    hub reachable -> the network gate returns with ZERO new behavior — no ip/
#                   ifreload/dhclient fake is ever touched (B'-style invariant).
#   G2 sweep-ok:    hub unreachable + no state.json -> the sweep re-points vmbr0 to the NIC that
#                   reaches the hub, persists atomically (winner + inet dhcp; original saved as
#                   interfaces.felhom-bak), logs "network self-heal", and PROCEEDS to pairing.
#   G3 sweep-fail:  no NIC reaches the hub -> console screen painted AND the interfaces file is
#                   BYTE-IDENTICAL (a failed sweep must never leave a half-rewritten config).
#   G4 state-file:  state.json present -> screen + retry only; the sweep is NEVER invoked
#                   (ifreload/dhclient call-count zero), interfaces untouched.
# (Updated for v1.21.0/R-33: the old one-poll-per-invocation scenarios expected a non-zero exit on
# 204, which the in-script wait deliberately no longer does — waiting is not failing.)
set -uo pipefail
BSTRAP=/work/felhom-bootstrap.sh
FAKE=/work/fakebin; rm -rf "$FAKE"; mkdir -p "$FAKE"
CALLS=/work/curl.log
export PATH="$FAKE:$PATH"
fail=0
say() { echo "TEST: $*"; }
check() { if eval "$2"; then echo "  ok: $1"; else echo "  FAIL: $1"; fail=1; fi; }

# --- fake sleep: counts waits; flips the poll to 200 after 3, hard-aborts a runaway loop. The gate
#     scenarios (G3/G4) use /work/sleep.abort to end the deliberately-infinite screen+retry loop. ----
cat > "$FAKE/sleep" <<'SLEEP'
#!/bin/bash
n=$(cat /work/sleep.count 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > /work/sleep.count
flip=$(cat /work/sleep.flip 2>/dev/null || echo "")
[ -n "$flip" ] && [ "$n" -ge "$flip" ] && echo 200 > /work/poll-mode
abort=$(cat /work/sleep.abort 2>/dev/null || echo 25)
# abort only on WAIT-length sleeps (>=30s: the poll/retry waits) — never the gate's 2s diag settle,
# which runs inside a command substitution: killing that subshell would blank the last screen paint.
if [ "$n" -gt "$abort" ] && [ "${1:-0}" -ge 30 ]; then
  echo "ABORT: fake sleep hit $n calls — killing the loop" >&2; kill -TERM $PPID
fi
exit 0
SLEEP
chmod +x "$FAKE/sleep"

# --- gate fakes: ip / ifreload / dhclient log their calls; dhclient rc comes from /work/dhcp-mode ---
cat > "$FAKE/ip" <<'IP'
#!/bin/bash
echo "$*" >> /work/ip.log
exit 0
IP
cat > "$FAKE/ifreload" <<'IFR'
#!/bin/bash
echo "$*" >> /work/ifreload.log
exit 0
IFR
cat > "$FAKE/dhclient" <<'DH'
#!/bin/bash
echo "$*" >> /work/dhclient.log
exit "$(cat /work/dhcp-mode 2>/dev/null || echo 0)"
DH
chmod +x "$FAKE/ip" "$FAKE/ifreload" "$FAKE/dhclient"

# --- fake curl: logs every invocation's URL; emulates -o (fetch), --data (register), -w code (poll) --
cat > "$FAKE/curl" <<'CURL'
#!/bin/bash
url=""; ofile=""; wfmt=""
prev=""
for a in "$@"; do
  case "$a" in http*|https*) url="$a";; esac
  case "$prev" in -o) ofile="$a";; -w) wfmt="$a";; esac
  prev="$a"
done
echo "$url" >> /work/curl.log
mode=$(cat /work/poll-mode 2>/dev/null || echo 204)
case "$url" in
  *"hub.example/")
      # the network gate's reachability probe (-o /dev/null -w '%{http_code}').
      # /work/hub-mode: absent|up -> 302; down -> 000/exit 7; follow:<nic> -> up only when the
      # interfaces fixture has bridge-ports <nic> (the sweep's "this NIC reaches the hub" oracle).
      hm=$(cat /work/hub-mode 2>/dev/null || echo up)
      case "$hm" in
        follow:*) if grep -q "bridge-ports ${hm#follow:}\$" /work/interfaces 2>/dev/null; then hm=up; else hm=down; fi ;;
      esac
      if [ "$hm" = "up" ]; then printf '302'; exit 0; else printf '000'; exit 7; fi ;;
esac
case "$url" in
  *"/felhom-host-install.sh")
      # write a stub host-install to the -o target
      cat > "$ofile" <<'HI'
#!/bin/bash
echo "fake host-install ran: $*" >> /work/hostinstall.log
exit 0
HI
      exit 0 ;;
  *"/appliance/register")
      echo '{"appliance_token":"TESTTOKEN123456","poll_interval_sec":30}'
      exit 0 ;;
  *"/appliance/poll")
      if [ "$mode" = "200" ]; then
        # body then, if -w set, a newline + code (matches the bootstrap's -w '\n%{http_code}')
        printf '%s' '{"customer_id":"drill","retrieval_passphrase":"SEKRET-PASS","mode":"appliance","extra_args":"--cores 2"}'
        [ -n "$wfmt" ] && printf '\n200'
      else
        [ -n "$wfmt" ] && printf '\n%s' "$mode"   # 204 / 410 / whatever the scenario set
      fi
      exit 0 ;;
esac
exit 0
CURL
chmod +x "$FAKE/curl"

# fake systemctl (disable is a no-op)
printf '#!/bin/bash\nexit 0\n' > "$FAKE/systemctl"; chmod +x "$FAKE/systemctl"

reset_state() {
  rm -rf /etc/felhom /run/felhom-bootstrap-pass /var/lib/felhom-install "$CALLS" /work/hostinstall.log \
         /work/poll-mode /work/sleep.count /work/sleep.flip /work/sleep.abort \
         /work/ip.log /work/ifreload.log /work/dhclient.log /work/hub-mode /work/dhcp-mode \
         /work/sys /work/interfaces /work/interfaces.felhom-bak /work/console.out \
         /run/felhom-interfaces.orig /work/run.log
  mkdir -p /etc/felhom
}

# gate fixture: two "physical" NICs (nicA = the configured bridge-port, nicB = the other one) and an
# interfaces file in the installer-fallback shape (static 192.168.100.2 on vmbr0 -> nicA).
make_gate_fixture() {
  mkdir -p /work/sys/nicA /work/sys/nicB /work/sys/vmbr0 /work/sys/lo
  touch /work/sys/nicA/device /work/sys/nicB/device        # only these two count as physical
  echo "aa:aa:aa:aa:aa:0a" > /work/sys/nicA/address; echo 1 > /work/sys/nicA/carrier; echo 1000 > /work/sys/nicA/speed
  echo "bb:bb:bb:bb:bb:0b" > /work/sys/nicB/address; echo 1 > /work/sys/nicB/carrier; echo -1  > /work/sys/nicB/speed
  cat > /work/interfaces <<'IFACES'
auto lo
iface lo inet loopback

auto vmbr0
iface vmbr0 inet static
	address 192.168.100.2/24
	bridge-ports nicA
	bridge-stp off
	bridge-fd 0

iface nicA inet manual

iface nicB inet manual

source /etc/network/interfaces.d/*
IFACES
}
GATE_ENV="FELHOM_NET_SYS=/work/sys FELHOM_INTERFACES_FILE=/work/interfaces FELHOM_CONSOLE_DEV=/work/console.out"

# ============================ Scenario D — direct mode, zero appliance calls =========================
# Runs WITH the gate fixture (NICs + fallback-shaped interfaces) and the hub reachable: the G1
# invariant below is only meaningful if the gate HAD candidates to touch and touched none.
say "D: direct env -> run_direct, NO appliance calls"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_CUSTOMER_ID=acme
FELHOM_MODE=appliance
FELHOM_RETRIEVAL_PASSPHRASE=direct-pass
FELHOM_HUB_URL=https://hub.example
ENV
chmod 0600 /etc/felhom/bootstrap.env
make_gate_fixture
env $GATE_ENV bash "$BSTRAP"; rc=$?
check "run_direct exited 0 (host-install stub succeeded)" "[ $rc -eq 0 ]"
check "host-install was invoked" "[ -f /work/hostinstall.log ]"
check "ZERO /appliance/register calls" "! grep -q '/appliance/register' $CALLS"
check "ZERO /appliance/poll calls"     "! grep -q '/appliance/poll' $CALLS"
check "done-flag written"              "[ -f /etc/felhom/.bootstrap-done ]"
check "env shredded on success"        "[ ! -f /etc/felhom/bootstrap.env ]"
# G1 (zero-new-behavior invariant): with the hub reachable, the gate returned before ANY network
# tooling was touched — no ip, no ifreload, no dhclient call exists.
check "G1: gate made zero ip calls"        "[ ! -f /work/ip.log ]"
check "G1: gate made zero ifreload calls"  "[ ! -f /work/ifreload.log ]"
check "G1: gate made zero dhclient calls"  "[ ! -f /work/dhclient.log ]"
check "G1: gate consumed zero sleeps"      "[ ! -f /work/sleep.count ]"
check "G1: interfaces fixture untouched"   "grep -q 'bridge-ports nicA' /work/interfaces && grep -q '192.168.100.2' /work/interfaces"

# ============ P: pairing loop (v1.21.0) — register, wait unbound INSIDE one invocation, deliver =====
say "P: pairing env -> register + in-script 204 wait -> 200 delivery -> host-install, ONE invocation"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_HUB_URL=https://hub.example
ENV
echo 204 > /work/poll-mode
echo 3 > /work/sleep.flip   # after 3 in-script waits the hub "binds" (poll flips to 200)
bash "$BSTRAP"; rc=$?
check "single invocation ran to done (exit 0)" "[ $rc -eq 0 ]"
check "POSTed /appliance/register"      "grep -q '/appliance/register' $CALLS"
check "appliance token persisted 0600"  "[ -f /etc/felhom/.bootstrap-done ] || { [ -f /etc/felhom/appliance-token ] && [ \"\$(stat -c %a /etc/felhom/appliance-token)\" = 600 ]; }"
check "polled more than once (the wait lived in-script, not in systemd restarts)" "[ \$(grep -c '/appliance/poll' $CALLS) -ge 2 ]"
check "waited between polls (fake sleep called >=3x)" "[ \"\$(cat /work/sleep.count 2>/dev/null || echo 0)\" -ge 3 ]"
check "host-install invoked after delivery"   "[ -f /work/hostinstall.log ]"
check "done-flag written"                     "[ -f /etc/felhom/.bootstrap-done ]"
# the token was ALSO consumed on success (run_direct scrubs both secrets)
check "env shredded on success"               "[ ! -f /etc/felhom/bootstrap.env ]"

# ============ 410: consumed delivery without a local env -> exit non-zero (crash window) ============
say "410: consumed delivery + no env -> exit non-zero so systemd restarts clean"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_HUB_URL=https://hub.example
ENV
echo 410 > /work/poll-mode
bash "$BSTRAP"; rc=$?
check "410 poll exits non-zero" "[ $rc -ne 0 ]"
check "host-install NOT run"    "[ ! -f /work/hostinstall.log ]"

# ============ G2: sweep-success — re-point to the hub-reaching NIC, persist, proceed ================
say "G2: hub down + no state.json -> sweep finds nicB, persists (bak kept), proceeds to pairing"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_HUB_URL=https://hub.example
ENV
make_gate_fixture
echo "follow:nicB" > /work/hub-mode      # hub reachable ONLY once vmbr0 points at nicB
echo 410 > /work/poll-mode               # after the gate, exit the pairing loop promptly
env $GATE_ENV bash "$BSTRAP" > /work/run.log 2>&1; rc=$?
check "sweep re-pointed vmbr0 to nicB"       "grep -q 'bridge-ports nicB' /work/interfaces"
check "winner persisted with DHCP addressing" "grep -q 'iface vmbr0 inet dhcp' /work/interfaces"
check "fallback static address dropped"       "! grep -q '192.168.100.2' /work/interfaces"
check "original saved as interfaces.felhom-bak" "[ -f /work/interfaces.felhom-bak ] && grep -q 'bridge-ports nicA' /work/interfaces.felhom-bak"
check "self-heal logged loudly"               "grep -q 'network self-heal: vmbr0 -> nicB' /work/run.log"
check "proceeded to pairing after the heal"   "grep -q '/appliance/register' $CALLS"
check "sweep exercised ifreload"              "[ -f /work/ifreload.log ]"

# ============ G3: sweep-fail — screen painted, interfaces BYTE-IDENTICAL ============================
say "G3: hub down everywhere -> console screen painted, interfaces byte-identical, no bak"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_HUB_URL=https://hub.example
ENV
make_gate_fixture
echo down > /work/hub-mode
echo 1 > /work/dhcp-mode                 # no NIC leases either
echo 8 > /work/sleep.abort               # end the deliberate screen+retry loop after a few cycles
PRE_SHA=$(sha256sum /work/interfaces | awk '{print $1}')
env $GATE_ENV bash "$BSTRAP" > /work/run.log 2>&1; rc=$?
POST_SHA=$(sha256sum /work/interfaces | awk '{print $1}')
check "console screen painted (header)"   "grep -q 'Nincs hálózati kapcsolat' /work/console.out"
check "screen lists the NICs"             "grep -q 'nicA' /work/console.out && grep -q 'nicB' /work/console.out"
check "screen names the fallback signature" "grep -q '192.168.100.2' /work/console.out"
check "screen carries the remedy line"    "grep -q 'percenként újra próbálkozik' /work/console.out"
check "interfaces BYTE-IDENTICAL after failed sweep" "[ \"$PRE_SHA\" = \"$POST_SHA\" ]"
check "no .felhom-bak on failure (success-only persist)" "[ ! -f /work/interfaces.felhom-bak ]"
check "host-install never ran"            "[ ! -f /work/hostinstall.log ]"

# ============ G4: state.json present — screen only, the sweep is NEVER invoked ======================
say "G4: hub down + state.json present -> screen + retry, sweep NEVER invoked, interfaces untouched"
reset_state
cat > /etc/felhom/bootstrap.env <<'ENV'
FELHOM_HUB_URL=https://hub.example
ENV
make_gate_fixture
mkdir -p /var/lib/felhom-install && touch /var/lib/felhom-install/state.json
echo down > /work/hub-mode
echo 8 > /work/sleep.abort
PRE_SHA=$(sha256sum /work/interfaces | awk '{print $1}')
env $GATE_ENV bash "$BSTRAP" > /work/run.log 2>&1; rc=$?
POST_SHA=$(sha256sum /work/interfaces | awk '{print $1}')
check "sweep NEVER invoked: zero ifreload calls" "[ ! -f /work/ifreload.log ]"
check "sweep NEVER invoked: zero dhclient calls" "[ ! -f /work/dhclient.log ]"
check "interfaces untouched"                     "[ \"$PRE_SHA\" = \"$POST_SHA\" ]"
check "console screen still painted"             "grep -q 'Nincs hálózati kapcsolat' /work/console.out"
check "no-sweep branch logged"                   "grep -q 'no sweep, interfaces untouched' /work/run.log"

echo "=================================================="
if [ $fail -eq 0 ]; then echo "ALL BOOTSTRAP-MODE TESTS PASSED"; else echo "SOME TESTS FAILED"; fi
exit $fail
