#!/usr/bin/env bash
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SCRIPT="$ROOT/backend-release.sh"
TESTROOT="${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/backend-release/run-$$-$RANDOM"
BACKEND="$TESTROOT/backend"
BACKEND_REAL="$TESTROOT/backend-real"
BIN="$TESTROOT/bin"
LOG="$TESTROOT/systemctl.log"
mkdir -p "$BIN" "$BACKEND_REAL"
ln -s "$BACKEND_REAL" "$BACKEND"
trap 'chmod -R u+w "$TESTROOT" 2>/dev/null; rm -rf "$TESTROOT"' EXIT

fails=0
check() {
  if (( $1 == 0 )); then printf 'ok - %s\n' "$2"
  else printf 'FAIL - %s\n' "$2"; fails=$((fails + 1)); fi
}

cat >"$BIN/systemctl" <<'SH'
#!/usr/bin/env bash
printf '%s\n' "$*" >>"$SYSTEMCTL_LOG"
if [[ " $* " == *" is-active "* ]]; then
  exit "${SYSTEMCTL_ACTIVE_RC:-1}"
fi
if [[ " $* " == *" is-enabled "* ]]; then
  exit "${SYSTEMCTL_ENABLED_RC:-1}"
fi
if [[ " $* " == *" cat "* ]]; then
  exit "${SYSTEMCTL_LOADED_RC:-0}"
fi
exit "${SYSTEMCTL_RESTART_RC:-0}"
SH
chmod +x "$BIN/systemctl"

cat >"$BIN/verify-release" <<'SH'
#!/usr/bin/env bash
component=$1 sha=$2
current=$(readlink "$OVERDECK_BACKEND_RELEASE_ROOT/$component/current") || exit 81
[[ "$current" == "releases/$sha" ]] || exit 82
[[ "$sha" != "${VERIFY_FAIL_SHA:-}" ]] || exit 83
[[ "${VERIFY_FAIL_ALL:-0}" != 1 ]] || exit 84
printf '%s:%s\n' "$component" "$sha" >>"$VERIFY_LOG"
SH
chmod +x "$BIN/verify-release"

run_release() {
  OVERDECK_BACKEND_RELEASE_ROOT="$BACKEND" \
    OVERDECK_BACKEND_RELEASE_LOCK_TIMEOUT="${LOCK_TIMEOUT:-1}" \
    SYSTEMCTL_LOG="$LOG" VERIFY_LOG="$TESTROOT/verify.log" \
    SYSTEMCTL_ACTIVE_RC="${SYSTEMCTL_ACTIVE_RC:-1}" \
    SYSTEMCTL_ENABLED_RC="${SYSTEMCTL_ENABLED_RC:-1}" \
    SYSTEMCTL_LOADED_RC="${SYSTEMCTL_LOADED_RC:-0}" \
    SYSTEMCTL_RESTART_RC="${SYSTEMCTL_RESTART_RC:-0}" \
    VERIFY_FAIL_SHA="${VERIFY_FAIL_SHA:-}" VERIFY_FAIL_ALL="${VERIFY_FAIL_ALL:-0}" \
    PATH="$BIN:$PATH" bash "$SCRIPT" "$@"
}

stage_release() {
  local component=$1 sha=$2 stage
  stage="$BACKEND/$component/.staging/$sha-$RANDOM"
  mkdir -p "$stage/bin"
  printf '#!/usr/bin/env bash\nexit 0\n' >"$stage/bin/runtime"
  chmod +x "$stage/bin/runtime"
  ln -s runtime "$stage/bin/runtime-link"
  run_release publish "$component" "$sha" "$stage"
}

A=$(printf 'a%.0s' {1..40})
B=$(printf 'b%.0s' {1..40})
C=$(printf 'c%.0s' {1..40})

A_PATH=$(stage_release collector "$A")
if [[ "$A_PATH" == "$BACKEND/collector/releases/$A" ]] \
  && run_release validate collector "$A" \
  && [[ ! -w "$A_PATH/release.json" ]] \
  && [[ "$(readlink "$A_PATH/bin/runtime-link")" == runtime ]]; then rc=0; else rc=1; fi
check "$rc" 'publish creates a complete read-only exact-SHA release'

run_release seed collector "$A"
if [[ "$(readlink "$BACKEND/collector/current")" == "releases/$A" ]]; then rc=0; else rc=1; fi
check "$rc" 'seed establishes the first validated current release'

stage_release collector "$B" >/dev/null
if run_release activate collector "$B" overdeck-collector.service "$BIN/verify-release" \
  && [[ "$(readlink "$BACKEND/collector/current")" == "releases/$B" ]] \
  && [[ "$(readlink "$BACKEND/collector/previous")" == "releases/$A" ]] \
  && grep -qx -- '--user restart overdeck-collector.service' "$LOG"; then rc=0; else rc=1; fi
check "$rc" 'activation switches current, verifies identity, and records previous'

stage_release collector "$C" >/dev/null
VERIFY_FAIL_SHA="$C"
export VERIFY_FAIL_SHA
run_release activate collector "$C" overdeck-collector.service "$BIN/verify-release" >/dev/null 2>&1
activate_rc=$?
unset VERIFY_FAIL_SHA
if [[ "$activate_rc" == 20 ]] \
  && [[ "$(readlink "$BACKEND/collector/current")" == "releases/$B" ]] \
  && [[ "$(tail -n 1 "$TESTROOT/verify.log")" == "collector:$B" ]]; then rc=0; else rc=1; fi
check "$rc" 'a rejected candidate restores and verifies the prior release'

VERIFY_FAIL_ALL=1
export VERIFY_FAIL_ALL
run_release activate collector "$C" overdeck-collector.service "$BIN/verify-release" >/dev/null 2>&1
activate_rc=$?
unset VERIFY_FAIL_ALL
if [[ "$activate_rc" == 21 ]] && [[ "$(readlink "$BACKEND/collector/current")" == "releases/$B" ]]; then rc=0; else rc=1; fi
check "$rc" 'a failed rollback is classified separately while leaving current on prior'

chmod u+w "$BACKEND/collector/releases/$C/release.json"
! run_release validate collector "$C" >/dev/null 2>&1
check $? 'mutable release content is refused'
chmod u-w "$BACKEND/collector/releases/$C/release.json"

chmod u+w "$BACKEND/collector/releases/$C"
ln -s "$TESTROOT" "$BACKEND/collector/releases/$C/escape"
chmod u-w "$BACKEND/collector/releases/$C"
! run_release validate collector "$C" >/dev/null 2>&1
check $? 'a release symlink cannot escape its artifact root'

! run_release validate '../collector' "$A" >/dev/null 2>&1
check $? 'component traversal is refused'
! run_release inspect collector '--system.service' active >/dev/null 2>&1
check $? 'systemctl option injection through a service name is refused'

exec {held}>"$BACKEND/collector/activation.lock"
flock "$held"
LOCK_TIMEOUT=0 run_release activate collector "$B" overdeck-collector.service "$BIN/verify-release" >/dev/null 2>&1
activate_rc=$?
if [[ "$activate_rc" == 22 ]]; then rc=0; else rc=1; fi
check "$rc" 'activation serializes and reports lock timeout'
flock -u "$held"

D=$(printf 'd%.0s' {1..40})
E=$(printf 'e%.0s' {1..40})
stage_release actions-gateway "$D" >/dev/null
stage_release actions-gateway "$E" >/dev/null
run_release seed actions-gateway "$D"
if run_release select-inactive actions-gateway "$E" actions-gateway.service \
  && [[ "$(readlink "$BACKEND/actions-gateway/current")" == "releases/$E" ]] \
  && [[ "$(readlink "$BACKEND/actions-gateway/previous")" == "releases/$D" ]]; then rc=0; else rc=1; fi
check "$rc" 'an inactive dark service selects exact current and preserves previous without restart'

SYSTEMCTL_ACTIVE_RC=0
export SYSTEMCTL_ACTIVE_RC
run_release select-inactive actions-gateway "$D" actions-gateway.service >/dev/null 2>&1
select_rc=$?
unset SYSTEMCTL_ACTIVE_RC
if [[ "$select_rc" == 65 ]] && [[ "$(readlink "$BACKEND/actions-gateway/current")" == "releases/$E" ]]; then rc=0; else rc=1; fi
check "$rc" 'inactive selection refuses to switch a running service'

collector_inspect=$(SYSTEMCTL_ACTIVE_RC=0 SYSTEMCTL_ENABLED_RC=0 \
  run_release inspect collector overdeck-collector.service active)
inspect_rc=$?
if [[ "$inspect_rc" == 0 && "$collector_inspect" == *'"state":"healthy"'* \
  && "$collector_inspect" == *'"current_sha":"'"$B"'"'* \
  && "$collector_inspect" == *'"previous_sha":"'"$A"'"'* \
  && "$collector_inspect" == *'"rollback_ready":true'* ]]; then rc=0; else rc=1; fi
check "$rc" 'inspection consolidates active identity and rollback readiness'

gateway_inspect=$(run_release inspect actions-gateway actions-gateway.service inactive)
inspect_rc=$?
if [[ "$inspect_rc" == 0 && "$gateway_inspect" == *'"state":"dark"'* \
  && "$gateway_inspect" == *'"active":false'* && "$gateway_inspect" == *'"enabled":false'* ]]; then rc=0; else rc=1; fi
check "$rc" 'inspection reports an installed inactive Gateway honestly as dark'

gateway_inspect=$(SYSTEMCTL_LOADED_RC=1 run_release inspect actions-gateway actions-gateway.service inactive)
inspect_rc=$?
if [[ "$inspect_rc" != 0 && "$gateway_inspect" == *'"state":"degraded"'* \
  && "$gateway_inspect" == *'"loaded":false'* ]]; then rc=0; else rc=1; fi
check "$rc" 'inspection degrades rather than inventing a missing installed unit'

P=$(printf '1%.0s' {1..40})
Q=$(printf '2%.0s' {1..40})
R=$(printf '3%.0s' {1..40})
stage_release pruner "$P" >/dev/null
stage_release pruner "$Q" >/dev/null
stage_release pruner "$R" >/dev/null
run_release seed pruner "$P"
run_release activate pruner "$Q" pruner.service "$BIN/verify-release" >/dev/null
young_output=$(run_release prune pruner 86400)
if [[ "$young_output" == *'"removed":0'* && -d "$BACKEND/pruner/releases/$R" ]]; then rc=0; else rc=1; fi
check "$rc" 'pruning retains a retired release younger than the safety age'
prune_output=$(run_release prune pruner 0)
prune_rc=$?
if [[ "$prune_rc" == 0 && "$prune_output" == *'"removed":1'* \
  && -d "$BACKEND/pruner/releases/$P" && -d "$BACKEND/pruner/releases/$Q" \
  && ! -e "$BACKEND/pruner/releases/$R" \
  && "$(readlink "$BACKEND/pruner/current")" == "releases/$Q" \
  && "$(readlink "$BACKEND/pruner/previous")" == "releases/$P" ]]; then rc=0; else rc=1; fi
check "$rc" 'pruning removes retired releases while preserving exact current and previous'

mkdir -p "$BACKEND/pruner/releases/not-a-release"
run_release prune pruner 0 >"$TESTROOT/prune.out" 2>"$TESTROOT/prune.err"
prune_rc=$?
if [[ "$prune_rc" != 0 && -d "$BACKEND/pruner/releases/not-a-release" \
  && "$(<"$TESTROOT/prune.out")" == *'"status":"degraded"'* \
  && "$(<"$TESTROOT/prune.out")" == *'"skipped":1'* ]]; then rc=0; else rc=1; fi
check "$rc" 'pruning refuses ambiguous entries and reports degraded cleanup'

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