#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_NAME="microhard-setup"
NODE_ID_CONF="/opt/microavia/config/node-ids.txt"
RADIO_CONFIG_TB_RC="/opt/microavia/config/mh-rc_6W.config"
RADIO_CONFIG_UAV="/opt/microavia/config/mh-uav_6W.config"
RADIO_CONFIG=""
MH_UPLOAD="/usr/local/bin/mh-upload"
MH_ASSIGN="/usr/local/bin/mh-assign"

# Program variables
RADIO_HOST="192.168.168.1"
FTP_USER="admin"
FTP_PASS="admin"
TELNET_USER="admin"
TELNET_PASS="microavia"
BYPASS="no"
# Network
SETUP_STATIC_IP="192.168.168.210/24"
REQUIRED_SUBNET_REGEX='^192\.168\.168\.'
IFACE_GLOBS=("enp*" "eth*" "usb*")
TARGET_IFACE=""
TARGET_CONN=""
STATIC_APPLIED="no"
STATE_DIR="/var/lib/microhard"
APT_FLAG="${STATE_DIR}/.microhard_deps_installed"
SETUP_FLAG="${STATE_DIR}/.mh_setup_done"
RADIO_PING_TIMEOUT_SECONDS="30"
DHCP_RESTORE_WAIT_SECONDS="5"

log()  { echo "[$SCRIPT_NAME] $(date '+%Y-%m-%d %H:%M:%S') INFO: $*"; }
warn() { echo "[$SCRIPT_NAME] $(date '+%Y-%m-%d %H:%M:%S') WARN: $*" >&2; }
error(){ echo "[$SCRIPT_NAME] $(date '+%Y-%m-%d %H:%M:%S') ERROR: $*" >&2; }

die() {
  error "$1"
  exit "${2:-1}"
}

on_error() {
  local rc=$?
  local line="${1:-unknown}"
  error "failed at line ${line}, exit code ${rc}"
  exit "$rc"
}

trap 'on_error $LINENO' ERR

restore_dhcp() {
  if [ "$STATIC_APPLIED" != "yes" ]; then
    return 0
  fi

  if [ -z "$TARGET_IFACE" ] || [ -z "$TARGET_CONN" ]; then
    warn "cannot restore DHCP: target interface or connection is unknown"
    return 0
  fi

  log "restoring DHCP automatic mode"
  log "interface: $TARGET_IFACE"
  log "connection: $TARGET_CONN"

  nmcli con mod "$TARGET_CONN" \
    ipv4.method auto \
    ipv4.addresses "" \
    ipv4.gateway "" \
    ipv4.dns "" \
    ipv6.method ignore || warn "failed to modify $TARGET_CONN back to DHCP"

  nmcli con up "$TARGET_CONN" ifname "$TARGET_IFACE" || warn "failed to bring DHCP connection up on $TARGET_IFACE"

  sleep "$DHCP_RESTORE_WAIT_SECONDS"

  log "DHCP restore requested on $TARGET_IFACE"
}

trap restore_dhcp EXIT

install_missing_dependencies() {
  mkdir -p "$STATE_DIR"
  local missing_cmds=()
  local missing_pkgs=()

  log "checking required dependencies"

  command -v ip       >/dev/null 2>&1 || { missing_cmds+=("ip");       missing_pkgs+=("iproute2"); }
  command -v ping     >/dev/null 2>&1 || { missing_cmds+=("ping");     missing_pkgs+=("iputils-ping"); }
  command -v nmcli    >/dev/null 2>&1 || { missing_cmds+=("nmcli");    missing_pkgs+=("network-manager"); }
  command -v lftp     >/dev/null 2>&1 || { missing_cmds+=("lftp");     missing_pkgs+=("lftp"); }
  command -v socat     >/dev/null 2>&1 || { missing_cmds+=("socat");     missing_pkgs+=("socat"); }
  command -v expect   >/dev/null 2>&1 || { missing_cmds+=("expect");   missing_pkgs+=("expect"); }
  command -v telnet   >/dev/null 2>&1 || { missing_cmds+=("telnet");   missing_pkgs+=("telnet"); }
  command -v awk      >/dev/null 2>&1 || { missing_cmds+=("awk");      missing_pkgs+=("gawk"); }
  command -v grep     >/dev/null 2>&1 || { missing_cmds+=("grep");     missing_pkgs+=("grep"); }
  command -v sed      >/dev/null 2>&1 || { missing_cmds+=("sed");      missing_pkgs+=("sed"); }
  command -v timeout  >/dev/null 2>&1 || { missing_cmds+=("timeout");  missing_pkgs+=("coreutils"); }

  if [ "${#missing_cmds[@]}" -eq 0 ]; then
    log "all dependencies are already installed"
    touch "$APT_FLAG"
    return 0
  fi

  warn "missing commands: ${missing_cmds[*]}"
  warn "installing packages: ${missing_pkgs[*]}"

  export DEBIAN_FRONTEND=noninteractive

  dpkg --configure -a
  apt-get update
  apt-get install --assume-yes -o Dpkg::Options::="--force-confnew" "${missing_pkgs[@]}"

  touch "$APT_FLAG"
  log "dependency installation complete"
}

check_required_files() {
  log "checking required package/config files"

  [ -f "$NODE_ID_CONF" ]      || die "missing node-ids.txt: $NODE_ID_CONF" 10
  [ -r "$NODE_ID_CONF" ]      || die "node-ids.txt is not readable: $NODE_ID_CONF" 11
  [ -f "$RADIO_CONFIG" ] || die "missing radio config: $RADIO_CONFIG" 12
  [ -r "$RADIO_CONFIG" ] || die "radio config is not readable: $RADIO_CONFIG" 13
  [ -x "$MH_UPLOAD" ]    || die "upload command missing or not executable: $MH_UPLOAD" 14
  [ -x "$MH_ASSIGN" ]    || die "assign command missing or not executable: $MH_ASSIGN" 15

  log "required files present"
}

iface_has_carrier() {
  local iface="$1"
  [ -r "/sys/class/net/$iface/carrier" ] || return 1
  [ "$(cat "/sys/class/net/$iface/carrier" 2>/dev/null)" = "1" ]
}

has_required_subnet_ip() {
  local iface="$1"
  ip -4 -o addr show dev "$iface" scope global 2>/dev/null \
    | awk '{print $4}' \
    | grep -qE "$REQUIRED_SUBNET_REGEX"
}

find_target_interface() {
  log "searching for linked enp*/eth*/usb* interface with IPv4 in 192.168.168.0/24"
  local glob path iface
  for glob in "${IFACE_GLOBS[@]}"; do
    for path in /sys/class/net/$glob; do
      [ -e "$path" ] || continue
      iface="$(basename "$path")"
      [ "$iface" = "lo" ] && continue
      log "checking interface candidate: $iface"

      if ! iface_has_carrier "$iface"; then
        warn "skipping $iface: no physical carrier/link"
        continue
      fi

      if ! has_required_subnet_ip "$iface"; then
        warn "skipping $iface: no IPv4 address in 192.168.168.0/24"
        ip -4 -o addr show dev "$iface" scope global 2>/dev/null || true
        continue
      fi

      TARGET_IFACE="$iface"
      log "selected target interface: $TARGET_IFACE"
      return 0
    done
  done

  die "no linked enp*/eth*/usb* interface found with IPv4 address in 192.168.168.0/24" 20
}

wait_for_radio_ping() {
  local elapsed=0
  log "checking radio reachability at $RADIO_HOST"

  while [ "$elapsed" -lt "$RADIO_PING_TIMEOUT_SECONDS" ]; do
    if ping -c 1 -W 1 -n "$RADIO_HOST" >/dev/null 2>&1; then
      log "radio is reachable at $RADIO_HOST"
      return 0
    fi

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

  die "radio is not reachable at $RADIO_HOST after ${RADIO_PING_TIMEOUT_SECONDS}s" 30
}

write_setup_flag() {
  mkdir -p "$STATE_DIR"
  {
    echo "setup_done_at=$(date '+%Y-%m-%dT%H:%M:%S%z')"
    echo "interface=$TARGET_IFACE"
    echo "connection=$TARGET_CONN"
    echo "radio_host=$RADIO_HOST"
    echo "rc_conf=$NODE_ID_CONF"
    echo "radio_config=$RADIO_CONFIG"
    echo "upload_command=$MH_UPLOAD"
    echo "assign_command=$MH_ASSIGN"
  } > "$SETUP_FLAG"
  log "setup flag written: $SETUP_FLAG"
}

# -----------------------------
# MAIN
# -----------------------------
START_TIME="$(date +%s)"
log "========== Microhard Setup Started =========="
log "node-ids.txt: $NODE_ID_CONF"
log "radio config: $RADIO_CONFIG"
log "upload command: $MH_UPLOAD"
log "assign command: $MH_ASSIGN"
log "setup flag: $SETUP_FLAG"
log "radio host: $RADIO_HOST"

if [ -f "$SETUP_FLAG" ]; then
  log "RC setup already completed. REASON: $SETUP_FLAG exists"
  log "exiting successfully"
  exit 0
fi

install_missing_dependencies
check_required_files
find_target_interface
#set_static_setup_ip "$TARGET_IFACE" "$TARGET_CONN"
wait_for_radio_ping
log "running modem config upload"
if [ "$DRONE_MODE" = "yes" ]; then
  RADIO_CONFIG="$RADIO_CONFIG_UAV"
  "$MH_UPLOAD" --config "$RADIO_CONFIG" --user "$FTP_USER" --pass "$FTP_PASS" --bypass "$BYPASS" --host "$RADIO_HOST" --drone
else
  RADIO_CONFIG="$RADIO_CONFIG_TB_RC"
  "$MH_UPLOAD" --config "$RADIO_CONFIG" --user "$FTP_USER" --pass "$FTP_PASS" --bypass "$BYPASS" --host "$RADIO_HOST"
fi
log "modem config upload completed"
wait_for_radio_ping
log "running modem assignment"
if [ "$DRONE_MODE" = "yes" ]; then
  RADIO_HOST="192.168.168.2"
fi
"$MH_ASSIGN" --config "$NODE_ID_CONF" --host "$RADIO_HOST" --user "$TELNET_USER" --pass "$TELNET_PASS" --bypass "$BYPASS"
log "modem assignment completed"
#restore_dhcp
write_setup_flag
END_TIME="$(date +%s)"
ELAPSED="$((END_TIME - START_TIME))"
log "microhard setup completed successfully"
log "elapsed seconds: $ELAPSED"
log "========== Microhard Setup: SUCCESS =========="

exit 0