#!/usr/bin/env bash
# Attempt to recover pos3↔pos4 bridge leg when link watch or operator detects deafness.
#
# soft  — safe while pog422_fixture_bridge.py is running (no br0 teardown):
#         flush peer ARP, refresh diag IP, query bridge STP, optional eth1 bounce.
# hard  — full L2 reinit (same as run_bridge_jun11_good.sh eth prep):
#         STOP bridge Python first — masterless_br0, diag IP, disable EEE.
#
# Usage:
#   sudo FIXTURE_POS=4 INTERFACE=br0 ./br0_link_recover.sh soft
#   sudo FIXTURE_POS=4 LOG_DIR=./logs ./br0_link_recover.sh hard

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MODE="${1:-soft}"
INTERFACE="${INTERFACE:-br0}"
DEVICE="${DEVICE:-${INTERFACE}}"
FIXTURE_POS="${FIXTURE_POS:-}"
JUN11_ETH="${JUN11_ETH:-}"
LOG_DIR="${LOG_DIR:-${ROOT}/logs}"
RECOVER_LOG="${RECOVER_LOG:-${LOG_DIR}/link_recover.log}"

if [[ "${EUID}" -ne 0 ]]; then
  echo "FATAL: run with sudo" >&2
  exit 1
fi

if [[ -z "${FIXTURE_POS}" ]] || ! [[ "${FIXTURE_POS}" =~ ^[34]$ ]]; then
  echo "FATAL: FIXTURE_POS=3 or 4 required" >&2
  exit 1
fi

if [[ "${FIXTURE_POS}" == "3" ]]; then
  LOCAL_IP="169.254.3.3"
  PEER_IP="169.254.4.4"
else
  LOCAL_IP="169.254.4.4"
  PEER_IP="169.254.3.3"
fi

mkdir -p "${LOG_DIR}"

log() {
  local line="[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"
  echo "${line}"
  echo "${line}" >> "${RECOVER_LOG}"
}

carrier_of() {
  local port="$1"
  if [[ -f "/sys/class/net/${port}/carrier" ]]; then
    cat "/sys/class/net/${port}/carrier" 2>/dev/null || echo "?"
  else
    echo "?"
  fi
}

probe_peer() {
  if command -v arping >/dev/null 2>&1; then
    arping -I "${DEVICE}" -c 1 -w 1 "${PEER_IP}" >/dev/null 2>&1
    return $?
  fi
  ping -I "${DEVICE}" -c 1 -W 1 "${PEER_IP}" >/dev/null 2>&1
}

soft_recover() {
  log "br0_link_recover soft start FIXTURE_POS=${FIXTURE_POS} dev=${DEVICE} peer=${PEER_IP}"

  log "neigh before: $(ip neigh show dev "${DEVICE}" "${PEER_IP}" 2>/dev/null || echo none)"
  ip neigh del "${PEER_IP}" dev "${DEVICE}" 2>/dev/null || true
  ip neigh flush dev "${DEVICE}" nud failed 2>/dev/null || true
  ip neigh flush dev "${DEVICE}" nud incomplete 2>/dev/null || true

  FIXTURE_POS="${FIXTURE_POS}" DEVICE="${DEVICE}" bash "${ROOT}/br0_link_diag.sh" teardown >/dev/null 2>&1 || true
  FIXTURE_POS="${FIXTURE_POS}" DEVICE="${DEVICE}" bash "${ROOT}/br0_link_diag.sh" setup

  for dev in eth0 eth1; do
    log "  $(ip -br link show "${dev}" 2>/dev/null || echo "${dev}: missing")"
  done

  log "carrier eth0=$(carrier_of eth0) eth1=$(carrier_of eth1)"

  # Partner leg on pos4 is usually eth1; bounce only when carrier was up (PHY present).
  if [[ "$(carrier_of eth1)" == "1" ]]; then
    if command -v ethtool >/dev/null 2>&1; then
      ethtool -r eth1 >/dev/null 2>&1 && log "ethtool -r eth1: OK" || log "ethtool -r eth1: skip/fail"
    fi
    if [[ "${LINK_RECOVER_ETH_BOUNCE:-0}" == "1" ]]; then
      log "eth1 bounce (down/up)"
      ip link set eth1 down
      sleep 0.3
      ip link set eth1 up
      sleep 0.5
      log "carrier after bounce eth0=$(carrier_of eth0) eth1=$(carrier_of eth1)"
    fi
  else
    log "eth1 carrier down — skip bounce"
  fi

  if probe_peer; then
    log "br0_link_recover soft: peer ARP OK"
    exit 0
  fi
  log "br0_link_recover soft: peer ARP still fail"
  exit 1
}

hard_recover() {
  if pgrep -f "pog422_fixture_bridge.py.*--fixture-pos ${FIXTURE_POS}" >/dev/null 2>&1; then
    log "FATAL hard recover: bridge still running for pos${FIXTURE_POS} — stop it first" >&2
    exit 1
  fi

  log "br0_link_recover hard start FIXTURE_POS=${FIXTURE_POS} JUN11_ETH=${JUN11_ETH:-?}"

  # Flat layout: prep_eth_for_fixture.sh + siblings live beside this script.
  TIMING_ROOT="${ROOT}"
  if [[ "${JUN11_ETH}" == "directional" ]]; then
    bash "${TIMING_ROOT}/prep_eth_for_fixture.sh" 2>&1 | while read -r l; do log "  ${l}"; done
    FIXTURE_POS="${FIXTURE_POS}" DEVICE="${DEVICE}" bash "${ROOT}/br0_link_diag.sh" setup
  else
    bash "${TIMING_ROOT}/masterless_br0.sh" 2>&1 | while read -r l; do log "  ${l}"; done
    if command -v bridge >/dev/null 2>&1; then
      bridge link 2>&1 | while read -r l; do log "  ${l}"; done || true
    fi
    FIXTURE_POS="${FIXTURE_POS}" DEVICE="${INTERFACE}" bash "${ROOT}/br0_link_diag.sh" setup
  fi

  EEE_LOG_FILE="${LOG_DIR}/disable_eth_eee.log"
  EEE_LOG_FILE="${EEE_LOG_FILE}" FIXTURE_POS="${FIXTURE_POS}" bash "${ROOT}/disable_eth_eee.sh" \
    2>&1 | while read -r l; do log "  ${l}"; done || true

  if probe_peer; then
    log "br0_link_recover hard: peer ARP OK (restart bridge Python to re-bind sockets)"
    exit 0
  fi
  log "br0_link_recover hard: peer ARP still fail after br0 rebuild"
  exit 1
}

case "${MODE}" in
  soft) soft_recover ;;
  hard) hard_recover ;;
  *)
    echo "Usage: FIXTURE_POS=3|4 $0 soft|hard" >&2
    exit 1
    ;;
esac
