#!/usr/bin/env bash
# Uploads the Microhard config file only.
set -Eeuo pipefail

SCRIPT_NAME="microhard-config-upload"
LFTP_FLAG="/var/lib/microhard/.microhard_configured"
DEFAULT_HOST="192.168.168.1"
DEFAULT_ATTEMPTS="3"
DEFAULT_TIMEOUT="30"
DEFAULT_PASSIVE_MODE="on"
WAIT_REBOOT="yes"
REBOOT_GRACE_SECONDS="15"
REBOOT_DROP_TIMEOUT_SECONDS="30"
REBOOT_BACK_TIMEOUT_SECONDS="75"
STABLE_PING_COUNT="30"

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}"
}

usage() {
  cat <<'USAGE'
Usage:
  mh-upload --config <config-file> --user <ftp-user> --pass <ftp-pass> --bypass <yes|no> [options]

Required arguments:
  --config <config-file>     Full path to the Microhard config file to upload
  --user <ftp-user>          FTP username
  --pass <ftp-pass>          FTP password
  --bypass <yes|no>          yes = upload even if /var/lib/microhard/.microhard_configured exists
                             no  = skip upload if /var/lib/microhard/.microhard_configured exists

Options:
  --host <radio-ip>          Radio FTP IP address. Default: 192.168.168.1
  --attempts <count>         Upload attempts. Default: 3
  --timeout <seconds>        Timeout per upload attempt. Default: 180
  --passive-mode <on|off>    LFTP passive mode. Default: on
  -h, --help                 Show this help

Examples:
  sudo mh-upload \
    --config /opt/microavia/config/mh-rc_1w.config \
    --user admin \
    --pass admin \
    --bypass no

  sudo mh-upload \
    --config /opt/microavia/config/mh-rc_1w.config \
    --user admin \
    --pass admin \
    --bypass yes \
    --host 192.168.168.1 \
    --timeout 180 \
    --attempts 3 \
    --passive-mode on
USAGE
}

print_missing_usage_and_exit() {
  local missing=0

  if [ -z "${CONFIG_PATH:-}" ]; then
    warn "missing required argument: --config <config-file>"
    missing=1
  fi

  if [ -z "${FTP_USER:-}" ]; then
    warn "missing required argument: --user <ftp-user>"
    missing=1
  fi

  if [ -z "${FTP_PASS+x}" ]; then
    warn "missing required argument: --pass <ftp-pass>"
    missing=1
  fi

  if [ -z "${BYPASS:-}" ]; then
    warn "missing required argument: --bypass <yes|no>"
    missing=1
  fi

  if [ "$missing" -eq 1 ]; then
    echo >&2
    error "missing required arguments"
    echo >&2
    usage >&2
    exit 2
  fi
}

is_positive_integer() {
  case "$1" in
    ''|*[!0-9]*) return 1 ;;
    0) return 1 ;;
    *) return 0 ;;
  esac
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "required dependency is not installed or not in PATH: $1" 10
}

ping_host() {
  local host="$1"

  ping -c 1 -W 1 -n "$host" >/dev/null 2>&1
}

wait_for_ping_drop() {
  local host="$1"
  local timeout_seconds="$2"
  local elapsed=0

  log "waiting for radio ping to drop at ${host}; timeout=${timeout_seconds}s"

  while [ "$elapsed" -lt "$timeout_seconds" ]; do
    if ! ping_host "$host"; then
      log "radio ping dropped at ${host}; reboot/apply cycle detected after ${elapsed}s"
      return 0
    fi

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

  warn "radio ping did not drop within ${timeout_seconds}s"
  return 1
}

wait_for_stable_ping() {
  local host="$1"
  local timeout_seconds="$2"
  local required_stable="$3"
  local elapsed=0
  local stable_count=0

  log "waiting for stable radio ping at ${host}; timeout=${timeout_seconds}s, required consecutive pings=${required_stable}"

  while [ "$elapsed" -lt "$timeout_seconds" ]; do
    if ping_host "$host"; then
      stable_count=$((stable_count + 1))
      log "stable ping progress at ${host}: ${stable_count}/${required_stable}"

      if [ "$stable_count" -ge "$required_stable" ]; then
        log "radio ping is stable at ${host}"
        return 0
      fi
    else
      if [ "$stable_count" -gt 0 ]; then
        warn "stable ping counter reset at ${host}"
      fi
      stable_count=0
    fi

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

  warn "radio did not become stable at ${host} within ${timeout_seconds}s"
  return 1
}

post_upload_reboot_monitor() {
  local host="$1"

  if [ "$WAIT_REBOOT" != "yes" ]; then
    log "post-upload reboot/apply monitor disabled by internal setting WAIT_REBOOT=$WAIT_REBOOT"
    return 0
  fi

  log "modem config upload command completed successfully; entering post-upload reboot/apply monitor"
  log "known behavior: ping may remain alive for 10-15 seconds after upload before reboot/apply starts"
  log "grace period before drop detection: ${REBOOT_GRACE_SECONDS}s"

  sleep "$REBOOT_GRACE_SECONDS"

  if wait_for_ping_drop "$host" "$REBOOT_DROP_TIMEOUT_SECONDS"; then
    log "drop detected; waiting for radio to return"
    wait_for_stable_ping "$host" "$REBOOT_BACK_TIMEOUT_SECONDS" "$STABLE_PING_COUNT"
    return $?
  fi

  # Some firmware/modem config uploads may not cause a visible ping drop.
  # Do not fail immediately; require stable ping before accepting success.
  warn "no visible ping drop detected; verifying stable radio response instead"
  wait_for_stable_ping "$host" "$REBOOT_BACK_TIMEOUT_SECONDS" "$STABLE_PING_COUNT"
}

write_flag() {
  local flag_path="$1"
  local config_path="$2"
  local config_size_bytes="$3"
  local host="$4"
  local attempt="$5"
  local elapsed="$6"

  mkdir -p "$(dirname "$flag_path")"

  {
    echo "configured_at=$(date '+%Y-%m-%dT%H:%M:%S%z')"
    echo "config_path=$config_path"
    echo "config_size_bytes=$config_size_bytes"
    echo "host=$host"
    echo "attempt=$attempt"
    echo "elapsed_seconds=$elapsed"
  } > "$flag_path"

  log "radio=configured flag written: $flag_path"
}

# Defaults for optional arguments. Required args intentionally start empty.
HOST="$DEFAULT_HOST"
ATTEMPTS="$DEFAULT_ATTEMPTS"
TIMEOUT_SECONDS="$DEFAULT_TIMEOUT"
PASSIVE_MODE="$DEFAULT_PASSIVE_MODE"
CONFIG_PATH=""
FTP_USER=""
unset FTP_PASS || true
BYPASS=""

if [ "$#" -eq 0 ]; then
  error "missing required arguments"
  echo >&2
  usage >&2
  exit 2
fi

while [ "$#" -gt 0 ]; do
  case "$1" in
    --config)
      [ "$#" -ge 2 ] || { error "--config requires a value"; echo >&2; usage >&2; exit 2; }
      CONFIG_PATH="$2"
      shift 2
      ;;

    --user)
      [ "$#" -ge 2 ] || { error "--user requires a value"; echo >&2; usage >&2; exit 2; }
      FTP_USER="$2"
      shift 2
      ;;

    --pass)
      [ "$#" -ge 2 ] || { error "--pass requires a value"; echo >&2; usage >&2; exit 2; }
      FTP_PASS="$2"
      shift 2
      ;;

    --bypass)
      [ "$#" -ge 2 ] || { error "--bypass requires a value: yes or no"; echo >&2; usage >&2; exit 2; }
      BYPASS="$2"
      shift 2
      ;;

    --host)
      [ "$#" -ge 2 ] || { error "--host requires a value"; echo >&2; usage >&2; exit 2; }
      HOST="$2"
      shift 2
      ;;

    --attempts)
      [ "$#" -ge 2 ] || { error "--attempts requires a value"; echo >&2; usage >&2; exit 2; }
      ATTEMPTS="$2"
      shift 2
      ;;

    --timeout)
      [ "$#" -ge 2 ] || { error "--timeout requires a value"; echo >&2; usage >&2; exit 2; }
      TIMEOUT_SECONDS="$2"
      shift 2
      ;;

    --passive-mode)
      [ "$#" -ge 2 ] || { error "--passive-mode requires a value: on or off"; echo >&2; usage >&2; exit 2; }
      PASSIVE_MODE="$2"
      shift 2
      ;;

    -h|--help)
      usage
      exit 0
      ;;

    *)
      error "unknown argument: $1"
      echo >&2
      usage >&2
      exit 2
      ;;
  esac
done

print_missing_usage_and_exit

case "$BYPASS" in
  yes|no) ;;
  *)
    error "invalid --bypass value: $BYPASS. Expected: yes or no"
    echo >&2
    usage >&2
    exit 2
    ;;
esac

case "$PASSIVE_MODE" in
  on|off) ;;
  *)
    error "invalid --passive-mode value: $PASSIVE_MODE. Expected: on or off"
    echo >&2
    usage >&2
    exit 2
    ;;
esac

if ! is_positive_integer "$ATTEMPTS"; then
  error "invalid --attempts value: $ATTEMPTS. Expected positive integer"
  echo >&2
  usage >&2
  exit 2
fi

if ! is_positive_integer "$TIMEOUT_SECONDS"; then
  error "invalid --timeout value: $TIMEOUT_SECONDS. Expected positive integer seconds"
  echo >&2
  usage >&2
  exit 2
fi

if ! is_positive_integer "$REBOOT_GRACE_SECONDS"; then
  die "internal setting REBOOT_GRACE_SECONDS must be a positive integer" 3
fi

if ! is_positive_integer "$REBOOT_DROP_TIMEOUT_SECONDS"; then
  die "internal setting REBOOT_DROP_TIMEOUT_SECONDS must be a positive integer" 3
fi

if ! is_positive_integer "$REBOOT_BACK_TIMEOUT_SECONDS"; then
  die "internal setting REBOOT_BACK_TIMEOUT_SECONDS must be a positive integer" 3
fi

if ! is_positive_integer "$STABLE_PING_COUNT"; then
  die "internal setting STABLE_PING_COUNT must be a positive integer" 3
fi

case "$WAIT_REBOOT" in
  yes|no) ;;
  *)
    die "internal setting WAIT_REBOOT must be yes or no" 3
    ;;
esac

require_command lftp
require_command timeout
require_command stat
require_command du
require_command awk
require_command date
require_command ping
require_command telnet

[ -f "$CONFIG_PATH" ] || die "config file not found: $CONFIG_PATH" 12
[ -r "$CONFIG_PATH" ] || die "config file is not readable: $CONFIG_PATH" 13

if [ -f "$LFTP_FLAG" ] && [ "$BYPASS" = "no" ]; then
  log "modem is already configured. REASON: $LFTP_FLAG exists"
  log "bypass=no, skipping upload. Use --bypass yes to force upload."
  exit 0
fi

if [ -f "$LFTP_FLAG" ] && [ "$BYPASS" = "yes" ]; then
  warn "modem=configured flag already exists but bypass=yes, forcing upload: $LFTP_FLAG"
fi

FILE_SIZE_BYTES="$(stat -c '%s' "$CONFIG_PATH")"
FILE_SIZE_HUMAN="$(du -h "$CONFIG_PATH" | awk '{print $1}')"
START_TIME="$(date +%s)"

log "========== Microhard Config Upload Started =========="
log "target radio: $HOST"
log "config file: $CONFIG_PATH"
log "file size: ${FILE_SIZE_BYTES} bytes (${FILE_SIZE_HUMAN})"
log "flag path: $LFTP_FLAG"
log "bypass existing flag: $BYPASS"
log "timeout per attempt: ${TIMEOUT_SECONDS}s"
log "max attempts: ${ATTEMPTS}"
log "FTP passive mode: ${PASSIVE_MODE}"
log "post-upload reboot monitor: ${WAIT_REBOOT}"
log "reboot grace seconds: ${REBOOT_GRACE_SECONDS}"
log "drop timeout seconds: ${REBOOT_DROP_TIMEOUT_SECONDS}"
log "back timeout seconds: ${REBOOT_BACK_TIMEOUT_SECONDS}"
log "stable ping count: ${STABLE_PING_COUNT}"

attempt=1

while [ "$attempt" -le "$ATTEMPTS" ]; do
  log "modem config upload attempt $attempt/$ATTEMPTS started"

  set +e

  timeout --foreground "$TIMEOUT_SECONDS" \
    lftp -u "$FTP_USER","$FTP_PASS" "$HOST" <<EOF_LFTP
set net:timeout 10
set net:max-retries 2
set net:reconnect-interval-base 2
set ftp:passive-mode ${PASSIVE_MODE}
set xfer:clobber on
set xfer:log yes
set xfer:log-file /dev/stdout
set cmd:verbose yes
set cmd:trace yes
bin
put "${CONFIG_PATH}"
bye
EOF_LFTP

  rc=$?
  set -e

  if [ "$rc" -eq 0 ]; then
    if post_upload_reboot_monitor "$HOST"; then
      END_TIME="$(date +%s)"
      ELAPSED="$((END_TIME - START_TIME))"

      log "modem config upload completed successfully and radio is stable"

      write_flag \
        "$LFTP_FLAG" \
        "$CONFIG_PATH" \
        "$FILE_SIZE_BYTES" \
        "$HOST" \
        "$attempt" \
        "$ELAPSED"

      log "elapsed seconds: $ELAPSED"
      log "========== Microhard Config Upload: SUCCESS =========="
      exit 0
    fi

    warn "Config upload succeeded, but post-upload radio stability verification failed"
  elif [ "$rc" -eq 124 ]; then
    warn "modem config upload timed out after ${TIMEOUT_SECONDS}s"
  else
    warn "modem config upload failed with exit code $rc"
  fi

  if [ "$attempt" -lt "$ATTEMPTS" ]; then
    warn "retrying modem config upload in 3 seconds"
    sleep 3
  fi

  attempt=$((attempt + 1))
done

die "modem config upload failed after ${ATTEMPTS} attempts" 20