#!/bin/sh
set -u
umask 077

CONF="/etc/router-egress-emergency-refresh.conf"
RUNNER="/usr/local/sbin/router-egress-emergency-refresh.sh"
HOOK="/usr/local/sbin/router-egress-emergency-decision-hook.sh"
PLANNER="/usr/local/sbin/router-egress-hmn-plan-top5.sh"
REFRESH="/root/hmn/hmn-refresh-pool-safe.sh"
APPLY="/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh"
HELPER="/usr/local/lib/router-egress-recovery-state.sh"

HOOK_INIT="/etc/init.d/router-egress-emergency-decision"
WATCHER_INIT="/etc/init.d/router-egress-health-repair"

STATE_DIR="/var/lib/router-egress-recovery"
CACHE_DIR="/root/hmn/cache"
POOL="${CACHE_DIR}/ok-awg1-strict-foreign-latest.tsv"

SLOTS_INIT="/etc/init.d/router-egress-slots"
MAPPER_INIT="/etc/init.d/router-egress-mapper"

RUN_ID="$(date -u +%Y%m%d-%H%M%S)-$$"
BACKUP_ROOT="${STATE_DIR}/step050m07b-${RUN_ID}"
ROLLBACK="/root/rollback-step050m07b-${RUN_ID}.sh"

ROLLBACK_READY=false
ROLLBACK_DONE=false

HOOK_WAS_RUNNING=false
WATCHER_WAS_RUNNING=false

trace() {
  printf '__TRACE__ stage=%s\n' "$1"
}

fact() {
  printf '__FACT__ %s=%s\n' "$1" "$2"
}

block() {
  echo "__BLOCK_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__BLOCK_END__ $1"
}

json_block() {
  echo "__JSON_BEGIN__ $1"
  printf '%s\n' "$2"
  echo "__JSON_END__ $1"
}

bool_cmd() {
  if "$@" >/dev/null 2>&1; then
    printf true
  else
    printf false
  fi
}

config_bool() {
  name="$1"

  (
    . "$CONF"
    eval "value=\${$name:-false}"

    case "$value" in
      1|true|TRUE|yes|YES|on|ON)
        printf true
        ;;
      *)
        printf false
        ;;
    esac
  )
}

config_value() {
  name="$1"
  fallback="$2"

  (
    . "$CONF"
    eval "value=\${$name:-\$fallback}"
    printf '%s' "$value"
  )
}

repair_counter() {
  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_daily_repair_get
  )
}

state_value() {
  key="$1"
  fallback="$2"

  (
    unset REG_STATE_DIR
    . "$HELPER"
    reg_get_state "$key" "$fallback"
  )
}

strict_ping() {
  interface="$1"
  attempt=1

  while [ "$attempt" -le 3 ]; do
    if ping \
      -I "$interface" \
      -c 1 \
      -W 4 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))
    sleep 1
  done

  return 1
}

collect_slots() {
  prefix="$1"
  strict_all=true

  for interface in vpn1 vpn2 vpn3 vpn4 vpn5; do
    raw="$(
      wg show "$interface" endpoints 2>&1 ||
      true
    )"

    block "${prefix}_wg_endpoints_${interface}" "$raw"

    if strict_ping "$interface"; then
      strict=true
    else
      strict=false
      strict_all=false
    fi

    fact "${prefix}.strict.${interface}" "$strict"
  done

  fact "${prefix}.strict_all" "$strict_all"

  routes_all=true

  for table in 201 202 203 204 205; do
    route_text="$(
      ip route show table "$table" 2>&1 ||
      true
    )"

    block "${prefix}_route_${table}" "$route_text"

    if printf '%s\n' "$route_text" |
      grep -q '^default '
    then
      route=true
    else
      route=false
      routes_all=false
    fi

    fact "${prefix}.route.${table}" "$route"
  done

  fact "${prefix}.routes_all" "$routes_all"
}

restore_services_only() {
  if [ "$HOOK_WAS_RUNNING" = true ]; then
    "$HOOK_INIT" start >/dev/null 2>&1 || true
  fi

  if [ "$WATCHER_WAS_RUNNING" = true ]; then
    "$WATCHER_INIT" start >/dev/null 2>&1 || true
  fi
}

auto_rollback_on_exit() {
  rc="$?"

  trap - EXIT

  if [ "$rc" -ne 0 ] &&
     [ "$ROLLBACK_READY" = true ] &&
     [ "$ROLLBACK_DONE" != true ] &&
     [ -x "$ROLLBACK" ]
  then
    if sh "$ROLLBACK"; then
      ROLLBACK_DONE=true
      fact auto_rollback true
    else
      fact auto_rollback false
    fi
  elif [ "$rc" -ne 0 ]; then
    restore_services_only
    fact auto_rollback false
  fi

  exit "$rc"
}

trap auto_rollback_on_exit EXIT

trace required_contract

for path in \
  "$CONF" \
  "$RUNNER" \
  "$HOOK" \
  "$PLANNER" \
  "$REFRESH" \
  "$APPLY" \
  "$HELPER" \
  "$HOOK_INIT" \
  "$WATCHER_INIT" \
  "$POOL"
do
  [ -e "$path" ] || {
    echo "__ERROR__ missing=$path"
    exit 21
  }
done

for command_name in \
  sh \
  sed \
  grep \
  find \
  sort \
  cp \
  mv \
  rm \
  mkdir \
  rmdir \
  date \
  wc \
  sha256sum \
  uci \
  wg \
  ip \
  ping
do
  command -v "$command_name" >/dev/null 2>&1 || {
    echo "__ERROR__ command_missing=$command_name"
    exit 22
  }
done

COMMIT_ENABLED="$(config_bool EMERGENCY_COMMIT_ENABLED)"
DIRECT_ENABLED="$(config_bool EMERGENCY_DIRECT_FAILOPEN_ENABLED)"
CONFIRM_TOKEN="$(config_value EMERGENCY_CONFIRM_TOKEN '')"
EMERGENCY_LOG="$(
  config_value \
    EMERGENCY_LOG \
    /var/log/router-egress-emergency-refresh.log
)"

fact commit_enabled "$COMMIT_ENABLED"
fact direct_failopen_enabled "$DIRECT_ENABLED"

[ "$COMMIT_ENABLED" = true ] || {
  echo "__ERROR__ emergency_commit_not_enabled"
  exit 23
}

[ "$DIRECT_ENABLED" = false ] || {
  echo "__ERROR__ direct_failopen_must_remain_disabled"
  exit 24
}

[ -n "$CONFIRM_TOKEN" ] || {
  echo "__ERROR__ emergency_confirm_token_empty"
  exit 25
}

trace preflight_state

COUNTER_PRE="$(repair_counter)"
RUNNER_PRE="$("$RUNNER" --dry-run)"
HOOK_PRE="$("$HOOK")"
PLANNER_PRE="$("$PLANNER")"

POOL_HASH_PRE="$(
  sha256sum "$POOL" |
    sed 's/[[:space:]].*$//'
)"

POOL_MTIME_PRE="$(
  date -r "$POOL" +%s
)"

POOL_ROWS_PRE="$(
  wc -l < "$POOL" |
    tr -d ' '
)"

NETWORK_HASH_PRE="$(
  uci export network |
    sha256sum |
    sed 's/[[:space:]].*$//'
)"

STATE_MODE_PRE="$(state_value mode NORMAL)"
STATE_STATUS_PRE="$(
  state_value last_emergency_refresh_status NONE
)"
STATE_EPOCH_PRE="$(
  state_value last_emergency_refresh_epoch 0
)"

fact repair_counter_pre "$COUNTER_PRE"
fact pool_hash_pre "$POOL_HASH_PRE"
fact pool_mtime_pre "$POOL_MTIME_PRE"
fact pool_rows_pre "$POOL_ROWS_PRE"
fact network_hash_pre "$NETWORK_HASH_PRE"
fact state_mode_pre "$STATE_MODE_PRE"
fact state_status_pre "$STATE_STATUS_PRE"
fact state_epoch_pre "$STATE_EPOCH_PRE"

json_block runner_pre "$RUNNER_PRE"
json_block hook_pre "$HOOK_PRE"
json_block planner_pre "$PLANNER_PRE"

collect_slots pre

trace create_outer_backup

mkdir -p "$BACKUP_ROOT"

cp -a "$CACHE_DIR" "$BACKUP_ROOT/cache.before"
cp -a /etc/config/network "$BACKUP_ROOT/network.before"

if [ -f "$STATE_DIR/state.kv" ]; then
  cp -a "$STATE_DIR/state.kv" "$BACKUP_ROOT/state.kv.before"
  echo true > "$BACKUP_ROOT/state.kv.existed"
else
  echo false > "$BACKUP_ROOT/state.kv.existed"
fi

if [ -f "$STATE_DIR/emergency-decision-last.json" ]; then
  cp -a \
    "$STATE_DIR/emergency-decision-last.json" \
    "$BACKUP_ROOT/emergency-decision-last.json.before"

  echo true > "$BACKUP_ROOT/emergency-decision-last.existed"
else
  echo false > "$BACKUP_ROOT/emergency-decision-last.existed"
fi

find "$STATE_DIR" \
  -maxdepth 3 \
  -type f \
  -name 'rollback-egress*.sh' \
  2>/dev/null |
  sort \
  > "$BACKUP_ROOT/slot-rollbacks.before"

HOOK_WAS_RUNNING="$(bool_cmd "$HOOK_INIT" running)"
WATCHER_WAS_RUNNING="$(bool_cmd "$WATCHER_INIT" running)"

fact hook_was_running "$HOOK_WAS_RUNNING"
fact watcher_was_running "$WATCHER_WAS_RUNNING"

cat > "$ROLLBACK" <<EOF
#!/bin/sh
set -u
umask 077

STATE_DIR='$STATE_DIR'
CACHE_DIR='$CACHE_DIR'
BACKUP_ROOT='$BACKUP_ROOT'
HOOK_INIT='$HOOK_INIT'
WATCHER_INIT='$WATCHER_INIT'
SLOTS_INIT='$SLOTS_INIT'
MAPPER_INIT='$MAPPER_INIT'

HOOK_WAS_RUNNING='$HOOK_WAS_RUNNING'
WATCHER_WAS_RUNNING='$WATCHER_WAS_RUNNING'

rc=0

"\$HOOK_INIT" stop >/dev/null 2>&1 || true
"\$WATCHER_INIT" stop >/dev/null 2>&1 || true

find "\$STATE_DIR" \
  -maxdepth 3 \
  -type f \
  -name 'rollback-egress*.sh' \
  2>/dev/null |
  sort -r \
  > "\$BACKUP_ROOT/slot-rollbacks.after"

while IFS= read -r rollback_path; do
  [ -n "\$rollback_path" ] || continue

  if ! grep -Fxq \
    "\$rollback_path" \
    "\$BACKUP_ROOT/slot-rollbacks.before"
  then
    sh "\$rollback_path" || rc=1
  fi
done < "\$BACKUP_ROOT/slot-rollbacks.after"

if [ -d "\$BACKUP_ROOT/cache.before" ]; then
  rm -rf "\$CACHE_DIR"
  cp -a "\$BACKUP_ROOT/cache.before" "\$CACHE_DIR" || rc=1
fi

if [ "\$(cat "\$BACKUP_ROOT/state.kv.existed")" = true ]; then
  cp -a \
    "\$BACKUP_ROOT/state.kv.before" \
    "\$STATE_DIR/state.kv" ||
    rc=1
else
  rm -f "\$STATE_DIR/state.kv"
fi

if [ "\$(cat "\$BACKUP_ROOT/emergency-decision-last.existed")" = true ]; then
  cp -a \
    "\$BACKUP_ROOT/emergency-decision-last.json.before" \
    "\$STATE_DIR/emergency-decision-last.json" ||
    rc=1
else
  rm -f "\$STATE_DIR/emergency-decision-last.json"
fi

rm -rf \
  /var/lock/router-egress-emergency-refresh.lock \
  /tmp/hmn-refresh-pool-safe.lock \
  2>/dev/null ||
  true

if [ "\$rc" -ne 0 ] &&
   [ -f "\$BACKUP_ROOT/network.before" ]
then
  cp -a \
    "\$BACKUP_ROOT/network.before" \
    /etc/config/network ||
    rc=1

  /etc/init.d/network reload >/dev/null 2>&1 || rc=1
  sleep 8

  [ ! -x "\$SLOTS_INIT" ] ||
    "\$SLOTS_INIT" restart >/dev/null 2>&1 ||
    rc=1

  [ ! -x "\$MAPPER_INIT" ] ||
    "\$MAPPER_INIT" restart >/dev/null 2>&1 ||
    rc=1
fi

if [ "\$HOOK_WAS_RUNNING" = true ]; then
  "\$HOOK_INIT" start >/dev/null 2>&1 || rc=1
fi

if [ "\$WATCHER_WAS_RUNNING" = true ]; then
  "\$WATCHER_INIT" start >/dev/null 2>&1 || rc=1
fi

echo "STEP_050M07B rollback rc=\$rc"
exit "\$rc"
EOF

chmod 700 "$ROLLBACK"
ROLLBACK_READY=true

fact rollback "$ROLLBACK"
fact rollback_exists "$(bool_cmd test -x "$ROLLBACK")"
fact backup_root "$BACKUP_ROOT"

trace isolate_services

"$WATCHER_INIT" stop >/dev/null 2>&1 || true
"$HOOK_INIT" stop >/dev/null 2>&1 || true

sleep 2

fact watcher_stopped "$(
  if "$WATCHER_INIT" running >/dev/null 2>&1; then
    echo false
  else
    echo true
  fi
)"

fact hook_stopped "$(
  if "$HOOK_INIT" running >/dev/null 2>&1; then
    echo false
  else
    echo true
  fi
)"

LOG_SIZE_PRE="$(
  if [ -f "$EMERGENCY_LOG" ]; then
    wc -c < "$EMERGENCY_LOG" |
      tr -d ' '
  else
    echo 0
  fi
)"

fact emergency_log "$EMERGENCY_LOG"
fact emergency_log_size_pre "$LOG_SIZE_PRE"

trace execute_real_runner

RUNNER_STDERR="/tmp/step050m07b-runner-${RUN_ID}.stderr"

set +e

RUNNER_COMMIT="$(
  "$RUNNER" \
    --commit \
    --confirm "$CONFIRM_TOKEN" \
    2> "$RUNNER_STDERR"
)"

RUNNER_RC="$?"

set -e

fact runner_commit_rc "$RUNNER_RC"
json_block runner_commit "$RUNNER_COMMIT"

RUNNER_STDERR_TEXT="$(
  cat "$RUNNER_STDERR" 2>/dev/null ||
  true
)"

block runner_commit_stderr "$RUNNER_STDERR_TEXT"
rm -f "$RUNNER_STDERR"

if printf '%s\n' "$RUNNER_COMMIT" |
  grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_ok_rebalance_ok"'
then
  fact refresh_ran true
  fact rebalance_apply_ran true
else
  if printf '%s\n' "$RUNNER_COMMIT" |
    grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_'
  then
    fact refresh_ran true
  else
    fact refresh_ran false
  fi

  if printf '%s\n' "$RUNNER_COMMIT" |
    grep -q 'rebalance'
  then
    fact rebalance_apply_ran true
  else
    fact rebalance_apply_ran false
  fi
fi

trace restore_services

if [ "$HOOK_WAS_RUNNING" = true ]; then
  "$HOOK_INIT" start >/dev/null 2>&1 || true
fi

if [ "$WATCHER_WAS_RUNNING" = true ]; then
  "$WATCHER_INIT" start >/dev/null 2>&1 || true
fi

sleep 2

fact hook_restored "$(bool_cmd "$HOOK_INIT" running)"
fact watcher_restored "$(bool_cmd "$WATCHER_INIT" running)"
fact hook_enabled "$(bool_cmd "$HOOK_INIT" enabled)"
fact watcher_enabled "$(bool_cmd "$WATCHER_INIT" enabled)"

trace post_state

COUNTER_POST="$(repair_counter)"
RUNNER_POST="$("$RUNNER" --dry-run)"
HOOK_POST="$("$HOOK")"
PLANNER_POST="$("$PLANNER")"

POOL_HASH_POST="$(
  sha256sum "$POOL" |
    sed 's/[[:space:]].*$//'
)"

POOL_MTIME_POST="$(
  date -r "$POOL" +%s
)"

POOL_ROWS_POST="$(
  wc -l < "$POOL" |
    tr -d ' '
)"

NETWORK_HASH_POST="$(
  uci export network |
    sha256sum |
    sed 's/[[:space:]].*$//'
)"

STATE_MODE_POST="$(state_value mode UNKNOWN)"
STATE_STATUS_POST="$(
  state_value last_emergency_refresh_status UNKNOWN
)"
STATE_EPOCH_POST="$(
  state_value last_emergency_refresh_epoch 0
)"

fact repair_counter_post "$COUNTER_POST"
fact pool_hash_post "$POOL_HASH_POST"
fact pool_mtime_post "$POOL_MTIME_POST"
fact pool_rows_post "$POOL_ROWS_POST"
fact network_hash_post "$NETWORK_HASH_POST"
fact state_mode_post "$STATE_MODE_POST"
fact state_status_post "$STATE_STATUS_POST"
fact state_epoch_post "$STATE_EPOCH_POST"

json_block runner_post "$RUNNER_POST"
json_block hook_post "$HOOK_POST"
json_block planner_post "$PLANNER_POST"

collect_slots post

LOG_SIZE_POST="$(
  if [ -f "$EMERGENCY_LOG" ]; then
    wc -c < "$EMERGENCY_LOG" |
      tr -d ' '
  else
    echo 0
  fi
)"

fact emergency_log_size_post "$LOG_SIZE_POST"

LOG_TAIL="$(
  tail -n 240 "$EMERGENCY_LOG" 2>/dev/null ||
  true
)"

block emergency_log_tail "$LOG_TAIL"

fact emergency_lock_present "$(
  bool_cmd test -e /var/lock/router-egress-emergency-refresh.lock
)"

fact hmn_refresh_lock_present "$(
  bool_cmd test -e /tmp/hmn-refresh-pool-safe.lock
)"

fact direct_failopen_changed false
fact mutation_started true

if [ "$RUNNER_RC" -ne 0 ]; then
  echo "__ERROR__ runner_commit_rc=$RUNNER_RC"
  exit 31
fi

if ! printf '%s\n' "$RUNNER_COMMIT" |
  grep -q '"decision"[[:space:]]*:[[:space:]]*"refresh_ok_rebalance_ok"'
then
  echo "__ERROR__ runner_decision_not_success"
  exit 32
fi

trace complete

trap - EXIT
exit 0
