#!/usr/bin/env bash

set -o pipefail

##################################################
# Aqara Gateway 網關更新管理工具
##################################################

VERSION="1.2-stable"
PLATFORM="Unknown"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

CSV_FILE="${SCRIPT_DIR}/devices.csv"
COMMANDS_FILE="${SCRIPT_DIR}/update_commands.conf"
SETTINGS_FILE="${SCRIPT_DIR}/setting.csv"
LOG_DIR="${SCRIPT_DIR}/logs"

TELNET_BIN=""
EXPECT_BIN=""

REBOOT_WAIT=30
TELNET_CONNECT_TIMEOUT=10
TELNET_LOGIN_TIMEOUT=120
PING_INTERVAL=3
PARALLEL_UPDATE_LIMIT=2

SUCCESS_LIST=()
FAIL_LIST=()
missing_files=()

LOCATIONS=()

DEVICE_IPS=()
DEVICE_PORTS=()
DEVICE_USERS=()
DEVICE_MODELS=()
DEVICE_LOCATIONS=()
DEVICE_NAMES=()

ALL_IPS=()
ALL_PORTS=()
ALL_USERS=()
ALL_MODELS=()
ALL_LOCATIONS=()
ALL_NAMES=()

########################################
# 顏色
########################################

RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
BLUE="\033[0;34m"
NC="\033[0m"

########################################
# OS 偵測
########################################

detect_os() {
  case "$(uname -s)" in
    Darwin)
      PLATFORM="macOS"
      ;;
    Linux)
      PLATFORM="Linux"
      ;;
    *)
      PLATFORM="Unknown"
      ;;
  esac
}

########################################
# 需求檢查
########################################

check_requirements() {
  TELNET_BIN="$(command -v telnet || true)"
  EXPECT_BIN="$(command -v expect || true)"

  local missing=0

  if [[ -z "$TELNET_BIN" ]]; then
    echo -e "${RED}系統缺少 telnet${NC}"
    missing=1
  fi

  if [[ -z "$EXPECT_BIN" ]]; then
    echo -e "${RED}系統缺少 expect${NC}"
    missing=1
  fi

  if [[ "$missing" == "1" ]]; then
    echo

    if [[ "$PLATFORM" == "macOS" ]]; then
      echo "macOS 安裝方式："
      echo "brew install telnet expect"
    elif [[ "$PLATFORM" == "Linux" ]]; then
      if command -v apt >/dev/null 2>&1; then
        echo "Linux 安裝方式："
        echo "sudo apt install telnet expect"
      elif command -v dnf >/dev/null 2>&1; then
        echo "Linux 安裝方式："
        echo "sudo dnf install telnet expect"
      elif command -v yum >/dev/null 2>&1; then
        echo "Linux 安裝方式："
        echo "sudo yum install telnet expect"
      else
        echo "請手動安裝 telnet 與 expect"
      fi
    else
      echo "未知系統，請手動安裝 telnet 與 expect"
    fi

    echo
    exit 1
  fi
}

########################################
# 基本工具
########################################

trim() {
  local s="$1"
  s="${s#"${s%%[![:space:]]*}"}"
  s="${s%"${s##*[![:space:]]}"}"
  printf '%s' "$s"
}

pause_return() {
  echo
  read -rp "按 Enter 返回..."
}

safe_filename() {
  local s="$1"
  s="${s// /_}"
  s="${s//\//_}"
  printf '%s' "$s"
}

contains_in_array() {
  local needle="$1"
  shift
  local item
  for item in "$@"; do
    [[ "$item" == "$needle" ]] && return 0
  done
  return 1
}

model_display() {
  case "$1" in
    M2) echo "M2 非PoE" ;;
    M2_POE) echo "M2 POE" ;;
    M3) echo "M3" ;;
    M200) echo "M200" ;;
    *) echo "$1" ;;
  esac
}

clamp_parallel_limit() {
  if [[ -z "$PARALLEL_UPDATE_LIMIT" ]]; then
    PARALLEL_UPDATE_LIMIT=2
  fi

  if ! [[ "$PARALLEL_UPDATE_LIMIT" =~ ^[0-9]+$ ]]; then
    PARALLEL_UPDATE_LIMIT=2
  fi

  if (( PARALLEL_UPDATE_LIMIT < 2 )); then
    PARALLEL_UPDATE_LIMIT=2
  fi

  if (( PARALLEL_UPDATE_LIMIT > 20 )); then
    PARALLEL_UPDATE_LIMIT=20
  fi
}

ensure_log_dir() {
  mkdir -p "$LOG_DIR"
}

########################################
# Header
########################################

print_header() {
  clear
  echo "========================================"
  echo "Aqara Gateway 網關更新管理工具 v${VERSION}"
  echo "========================================"
  echo "OS: ${PLATFORM} | Bash: ${BASH_VERSION}"
  echo "========================================"
  echo
}

print_section() {
  local title="$1"
  print_header
  echo "$title"
  echo "--------------------------------"
}

########################################
# 設定檔檢查
########################################

has_device_data() {
  [[ -f "$CSV_FILE" ]] || return 1

  local ip port username model location name

  while IFS=',' read -r ip port username model location name; do
    ip="$(trim "${ip:-}")"
    [[ "$ip" == "ip" ]] && continue
    [[ -n "$ip" ]] && return 0
  done < "$CSV_FILE"

  return 1
}

has_model_commands() {
  [[ -f "$COMMANDS_FILE" ]] || return 1
  grep -q '^[[:space:]]*\[.*\][[:space:]]*$' "$COMMANDS_FILE"
}

refresh_missing_files() {
  missing_files=()

  [[ -f "$CSV_FILE" ]] || missing_files+=("devices")
  [[ -f "$COMMANDS_FILE" ]] || missing_files+=("commands")
}

can_update_gateways() {
  refresh_missing_files

  [[ ${#missing_files[@]} -eq 0 ]] || return 1
  has_device_data || return 1
  has_model_commands || return 1

  return 0
}

show_missing_files_notice() {
  refresh_missing_files

  local shown=0
  local device_notice_shown=0
  local command_notice_shown=0

  if [[ ${#missing_files[@]} -gt 0 ]]; then
    echo -e "${YELLOW}設定缺失資料,請先到「設定」建立檔案資料${NC}"
    shown=1

    local item
    for item in "${missing_files[@]}"; do
      case "$item" in
        devices)
          echo -e "${YELLOW}- 設備未設定${NC}"
          device_notice_shown=1
          ;;
        commands)
          echo -e "${YELLOW}- 網關型號更新指令未設定${NC}"
          command_notice_shown=1
          ;;
      esac
    done
  fi

  if [[ "$device_notice_shown" == "0" ]] && [[ -f "$CSV_FILE" ]] && ! has_device_data; then
    echo -e "${YELLOW}- 設備未設定${NC}"
    shown=1
  fi

  if [[ "$command_notice_shown" == "0" ]] && [[ -f "$COMMANDS_FILE" ]] && ! has_model_commands; then
    echo -e "${YELLOW}- 網關型號更新指令未設定${NC}"
    shown=1
  fi

  if [[ "$shown" == "1" ]]; then
    echo
  fi
}

ensure_csv_file() {
  if [[ ! -f "$CSV_FILE" ]]; then
    echo "ip,port,username,model,location,name" > "$CSV_FILE"
  fi
}

ensure_commands_file() {
  if [[ ! -f "$COMMANDS_FILE" ]]; then
    : > "$COMMANDS_FILE"
  fi
}

ensure_settings_file() {
  if [[ ! -f "$SETTINGS_FILE" ]]; then
    cat > "$SETTINGS_FILE" <<'EOF'
key,value
REBOOT_WAIT,30
TELNET_CONNECT_TIMEOUT,10
TELNET_LOGIN_TIMEOUT,120
PING_INTERVAL,3
PARALLEL_UPDATE_LIMIT,2
EOF
  fi
}

########################################
# setting.csv 讀取
########################################

load_settings() {
  [[ ! -f "$SETTINGS_FILE" ]] && return

  local key value

  while IFS=',' read -r key value; do
    key="$(trim "$key")"
    value="$(trim "$value")"

    [[ "$key" == "key" ]] && continue
    [[ -z "$key" ]] && continue

    case "$key" in
      REBOOT_WAIT) REBOOT_WAIT="$value" ;;
      TELNET_CONNECT_TIMEOUT) TELNET_CONNECT_TIMEOUT="$value" ;;
      TELNET_LOGIN_TIMEOUT) TELNET_LOGIN_TIMEOUT="$value" ;;
      PING_INTERVAL) PING_INTERVAL="$value" ;;
      PARALLEL_UPDATE_LIMIT) PARALLEL_UPDATE_LIMIT="$value" ;;
    esac
  done < "$SETTINGS_FILE"

  clamp_parallel_limit
}
########################################
# 連線判斷
########################################

is_private_ipv4() {
  local ip="$1"

  if [[ "$ip" =~ ^10\. ]]; then
    return 0
  fi

  if [[ "$ip" =~ ^192\.168\. ]]; then
    return 0
  fi

  if [[ "$ip" =~ ^172\.1[6-9]\. ]] || \
     [[ "$ip" =~ ^172\.2[0-9]\. ]] || \
     [[ "$ip" =~ ^172\.3[0-1]\. ]]; then
    return 0
  fi

  return 1
}

ping_once() {
  local host="$1"
  ping -c 1 -W 1 "$host" >/dev/null 2>&1
}

telnet_port_open() {
  local host="$1"
  local port="$2"

  "$EXPECT_BIN" <<EOF >/dev/null 2>&1
set timeout $TELNET_CONNECT_TIMEOUT
spawn $TELNET_BIN $host $port
expect {
    -re "(?i)login:|username:" { exit 0 }
    -re "Escape character is" { exit 0 }
    timeout { exit 1 }
    eof { exit 1 }
}
EOF
}

wait_for_reconnect() {
  local host="$1"
  local port="$2"
  local silent="${3:-0}"

  if [[ "$silent" != "1" ]]; then
    echo -e "${YELLOW}等待重啟完成中...${NC}"
  fi

  sleep "$REBOOT_WAIT"

  local i
  for ((i=1;i<=20;i++)); do
    if is_private_ipv4 "$host"; then
      if ping_once "$host"; then
        return 0
      fi
    else
      if telnet_port_open "$host" "$port"; then
        return 0
      fi
    fi

    sleep "$PING_INTERVAL"
  done

  return 1
}

########################################
# 版本與更新指令
########################################

get_updated_version() {
  local log_file="$1"
  grep "Update to" "$log_file" | tail -n1 | awk '{print $3}'
}

get_update_cmd() {
  local model="$1"

  awk -v target="[$model]" '
    $0 == target {found=1; next}
    /^\[.*\]$/ && found {exit}
    found {print}
  ' "$COMMANDS_FILE"
}

list_registered_models() {
  if [[ ! -f "$COMMANDS_FILE" ]]; then
    return
  fi

  awk '
    /^\[.*\]$/ {
      gsub(/^\[/, "", $0)
      gsub(/\]$/, "", $0)
      print $0
    }
  ' "$COMMANDS_FILE"
}

model_exists() {
  local model="$1"
  [[ -f "$COMMANDS_FILE" ]] && grep -Fxq "[$model]" "$COMMANDS_FILE"
}

########################################
# devices.csv 讀取
# 格式: ip,port,username,model,location,name
########################################

load_locations() {
  LOCATIONS=()
  [[ -f "$CSV_FILE" ]] || return

  local ip port username model location name

  while IFS=',' read -r ip port username model location name; do
    ip="$(trim "${ip:-}")"
    port="$(trim "${port:-}")"
    username="$(trim "${username:-}")"
    model="$(trim "${model:-}")"
    location="$(trim "${location:-}")"
    name="$(trim "${name:-}")"

    [[ "$ip" == "ip" ]] && continue
    [[ -z "$ip" ]] && continue
    [[ -z "$location" ]] && continue

    if ! contains_in_array "$location" "${LOCATIONS[@]:-}"; then
      LOCATIONS+=("$location")
    fi
  done < "$CSV_FILE"
}

load_devices_by_location() {
  local wanted_location="$1"

  DEVICE_IPS=()
  DEVICE_PORTS=()
  DEVICE_USERS=()
  DEVICE_MODELS=()
  DEVICE_LOCATIONS=()
  DEVICE_NAMES=()

  [[ -f "$CSV_FILE" ]] || return

  local ip port username model location name

  while IFS=',' read -r ip port username model location name; do
    ip="$(trim "${ip:-}")"
    port="$(trim "${port:-}")"
    username="$(trim "${username:-}")"
    model="$(trim "${model:-}")"
    location="$(trim "${location:-}")"
    name="$(trim "${name:-}")"

    [[ "$ip" == "ip" ]] && continue
    [[ -z "$ip" ]] && continue

    if [[ "$location" == "$wanted_location" ]]; then
      DEVICE_IPS+=("$ip")
      DEVICE_PORTS+=("${port:-23}")
      DEVICE_USERS+=("${username:-root}")
      DEVICE_MODELS+=("$model")
      DEVICE_LOCATIONS+=("$location")
      DEVICE_NAMES+=("$name")
    fi
  done < "$CSV_FILE"
}

load_all_devices() {
  ALL_IPS=()
  ALL_PORTS=()
  ALL_USERS=()
  ALL_MODELS=()
  ALL_LOCATIONS=()
  ALL_NAMES=()

  [[ -f "$CSV_FILE" ]] || return

  local ip port username model location name

  while IFS=',' read -r ip port username model location name; do
    ip="$(trim "${ip:-}")"
    port="$(trim "${port:-}")"
    username="$(trim "${username:-}")"
    model="$(trim "${model:-}")"
    location="$(trim "${location:-}")"
    name="$(trim "${name:-}")"

    [[ "$ip" == "ip" ]] && continue
    [[ -z "$ip" ]] && continue

    ALL_IPS+=("$ip")
    ALL_PORTS+=("${port:-23}")
    ALL_USERS+=("${username:-root}")
    ALL_MODELS+=("$model")
    ALL_LOCATIONS+=("$location")
    ALL_NAMES+=("$name")
  done < "$CSV_FILE"
}

########################################
# 查看功能
########################################

view_devices() {
  print_section "設備列表"

  if [[ ! -f "$CSV_FILE" ]]; then
    echo "devices.csv 尚未建立"
    pause_return
    return
  fi

  load_all_devices

  if [[ ${#ALL_IPS[@]} -eq 0 ]]; then
    echo "目前沒有設備資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    echo "$((i+1)). ${ALL_NAMES[$i]} | ${ALL_LOCATIONS[$i]} | $(model_display "${ALL_MODELS[$i]}") | ${ALL_IPS[$i]}:${ALL_PORTS[$i]} | ${ALL_USERS[$i]}"
    i=$((i + 1))
  done

  pause_return
}

view_models() {
  print_section "網關型號與更新指令"

  if [[ ! -f "$COMMANDS_FILE" ]]; then
    echo "update_commands.conf 尚未建立"
    pause_return
    return
  fi

  if ! grep -q '^\[.*\]$' "$COMMANDS_FILE"; then
    echo "目前沒有已登錄型號"
    pause_return
    return
  fi

  awk '
    /^\[.*\]$/ {
      print $0
      next
    }
    { print "  " $0 }
  ' "$COMMANDS_FILE"

  pause_return
}

view_settings() {
  print_section "setting.csv 設定"

  if [[ ! -f "$SETTINGS_FILE" ]]; then
    echo "setting.csv 尚未建立"
    pause_return
    return
  fi

  cat "$SETTINGS_FILE"
  pause_return
}
########################################
# 設備管理
########################################

add_device() {
  ensure_csv_file

  print_section "新增設備"

  read -rp "ip 請輸入設備IP或網域: " ip
  read -rp "port 請輸入連接埠 (預設23): " port
  port="${port:-23}"

  read -rp "username 請輸入登入帳號 (預設 root): " username
  username="${username:-root}"

  read -rp "model 請輸入網關型號: " model
  read -rp "location 請輸入地點: " location
  read -rp "name 請輸入設備名稱: " name

  echo
  echo "====== 請確認資料 ======"
  echo "IP       : $ip"
  echo "PORT     : $port"
  echo "USERNAME : $username"
  echo "MODEL    : $model"
  echo "LOCATION : $location"
  echo "NAME     : $name"
  echo "========================"

  read -rp "確認新增? (Y/N): " confirm
  if [[ "$confirm" != "Y" && "$confirm" != "y" ]]; then
    echo "取消新增"
    pause_return
    return
  fi

  echo "$ip,$port,$username,$model,$location,$name" >> "$CSV_FILE"

  echo
  echo -e "${GREEN}設備新增成功${NC}"
  pause_return
}

delete_device() {
  if [[ ! -f "$CSV_FILE" ]]; then
    print_section "刪除設備"
    echo "devices.csv 不存在"
    pause_return
    return
  fi

  load_all_devices
  print_section "刪除設備"

  if [[ ${#ALL_IPS[@]} -eq 0 ]]; then
    echo "沒有設備資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    echo "$((i+1)). ${ALL_NAMES[$i]} | ${ALL_IPS[$i]}"
    i=$((i+1))
  done

  echo "0. 取消"
  read -rp "請選擇要刪除的設備: " choice
  [[ "$choice" == "0" ]] && return

  local idx=$((choice-1))

  if [[ $idx -lt 0 || $idx -ge ${#ALL_IPS[@]} ]]; then
    echo "選項錯誤"
    pause_return
    return
  fi

  local name="${ALL_NAMES[$idx]}"

  read -rp "確定刪除 $name ? (Y/N): " confirm
  [[ "$confirm" != "Y" && "$confirm" != "y" ]] && return

  local tmp
  tmp=$(mktemp)
  head -n1 "$CSV_FILE" > "$tmp"

  i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    if [[ $i -ne $idx ]]; then
      echo "${ALL_IPS[$i]},${ALL_PORTS[$i]},${ALL_USERS[$i]},${ALL_MODELS[$i]},${ALL_LOCATIONS[$i]},${ALL_NAMES[$i]}" >> "$tmp"
    fi
    i=$((i+1))
  done

  mv "$tmp" "$CSV_FILE"

  echo -e "${GREEN}設備刪除成功${NC}"
  pause_return
}

edit_device() {
  if [[ ! -f "$CSV_FILE" ]]; then
    print_section "修改設備"
    echo "devices.csv 不存在"
    pause_return
    return
  fi

  load_all_devices
  print_section "修改設備"

  if [[ ${#ALL_IPS[@]} -eq 0 ]]; then
    echo "沒有設備資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    echo "$((i+1)). ${ALL_NAMES[$i]} | ${ALL_IPS[$i]}"
    i=$((i+1))
  done

  echo "0. 返回"
  read -rp "選擇設備: " choice
  [[ "$choice" == "0" ]] && return

  local idx=$((choice-1))

  if [[ $idx -lt 0 || $idx -ge ${#ALL_IPS[@]} ]]; then
    echo "選項錯誤"
    pause_return
    return
  fi

  local ip="${ALL_IPS[$idx]}"
  local port="${ALL_PORTS[$idx]}"
  local username="${ALL_USERS[$idx]}"
  local model="${ALL_MODELS[$idx]}"
  local location="${ALL_LOCATIONS[$idx]}"
  local name="${ALL_NAMES[$idx]}"

  echo
  echo "直接 Enter 表示不修改"

  read -rp "ip ($ip): " new_ip
  read -rp "port ($port): " new_port
  read -rp "username ($username): " new_user
  read -rp "model ($model): " new_model
  read -rp "location ($location): " new_location
  read -rp "name ($name): " new_name

  ip="${new_ip:-$ip}"
  port="${new_port:-$port}"
  username="${new_user:-$username}"
  model="${new_model:-$model}"
  location="${new_location:-$location}"
  name="${new_name:-$name}"

  local tmp
  tmp=$(mktemp)
  head -n1 "$CSV_FILE" > "$tmp"

  i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    if [[ $i -eq $idx ]]; then
      echo "$ip,$port,$username,$model,$location,$name" >> "$tmp"
    else
      echo "${ALL_IPS[$i]},${ALL_PORTS[$i]},${ALL_USERS[$i]},${ALL_MODELS[$i]},${ALL_LOCATIONS[$i]},${ALL_NAMES[$i]}" >> "$tmp"
    fi
    i=$((i+1))
  done

  mv "$tmp" "$CSV_FILE"

  echo -e "${GREEN}設備修改成功${NC}"
  pause_return
}

########################################
# 型號與更新指令管理
########################################

add_model() {
  ensure_commands_file

  print_section "新增網關型號與更新指令"

  echo "目前已登錄型號："
  list_registered_models
  echo

  read -rp "請輸入型號名稱: " model
  [[ -z "$model" ]] && return

  if model_exists "$model"; then
    echo "型號已存在"
    pause_return
    return
  fi

  echo
  echo "請輸入更新指令"
  echo "最後一行必須輸入 reboot"
  echo

  local commands=()

  while true; do
    read -rp "> " line
    commands+=("$line")
    [[ "$line" == "reboot" ]] && break
  done

  echo
  echo "====== 確認新增 ======"
  echo "型號: $model"
  echo "指令:"
  printf '%s\n' "${commands[@]}"
  echo "======================"

  read -rp "確認新增? (Y/N): " confirm
  [[ "$confirm" != "Y" && "$confirm" != "y" ]] && return

  {
    echo "[$model]"
    printf '%s\n' "${commands[@]}"
    echo
  } >> "$COMMANDS_FILE"

  echo -e "${GREEN}型號新增成功${NC}"
  pause_return
}

delete_model() {
  if [[ ! -f "$COMMANDS_FILE" ]]; then
    print_section "刪除網關型號"
    echo "update_commands.conf 不存在"
    pause_return
    return
  fi

  print_section "刪除網關型號"

  MODELS=()
  while IFS= read -r m; do
    [[ -n "$m" ]] && MODELS+=("$m")
  done < <(list_registered_models)

  if [[ ${#MODELS[@]} -eq 0 ]]; then
    echo "沒有型號資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#MODELS[@]} ]]; do
    echo "$((i+1)). ${MODELS[$i]}"
    i=$((i+1))
  done

  echo "0. 返回"
  read -rp "選擇型號: " choice
  [[ "$choice" == "0" ]] && return

  local idx=$((choice-1))

  if [[ $idx -lt 0 || $idx -ge ${#MODELS[@]} ]]; then
    echo "選項錯誤"
    pause_return
    return
  fi

  local model="${MODELS[$idx]}"

  read -rp "確定刪除 $model ? (Y/N): " confirm
  [[ "$confirm" != "Y" && "$confirm" != "y" ]] && return

  local tmp
  tmp=$(mktemp)

  awk -v target="[$model]" '
    $0 == target {skip=1; next}
    /^\[.*\]$/ {skip=0}
    !skip {print}
  ' "$COMMANDS_FILE" > "$tmp"

  mv "$tmp" "$COMMANDS_FILE"

  echo -e "${GREEN}型號刪除成功${NC}"
  pause_return
}

edit_model() {
  if [[ ! -f "$COMMANDS_FILE" ]]; then
    print_section "修改網關型號"
    echo "update_commands.conf 不存在"
    pause_return
    return
  fi

  print_section "修改網關型號"

  MODELS=()
  while IFS= read -r m; do
    [[ -n "$m" ]] && MODELS+=("$m")
  done < <(list_registered_models)

  if [[ ${#MODELS[@]} -eq 0 ]]; then
    echo "沒有型號資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#MODELS[@]} ]]; do
    echo "$((i+1)). ${MODELS[$i]}"
    i=$((i+1))
  done

  echo "0. 返回"
  read -rp "選擇型號: " choice
  [[ "$choice" == "0" ]] && return

  local idx=$((choice-1))

  if [[ $idx -lt 0 || $idx -ge ${#MODELS[@]} ]]; then
    echo "選項錯誤"
    pause_return
    return
  fi

  local model="${MODELS[$idx]}"

  echo
  echo "請重新輸入更新指令"
  echo "最後一行必須 reboot"

  local commands=()

  while true; do
    read -rp "> " line
    commands+=("$line")
    [[ "$line" == "reboot" ]] && break
  done

  local tmp
  tmp=$(mktemp)

  awk -v target="[$model]" '
    $0 == target {skip=1; next}
    /^\[.*\]$/ {skip=0}
    !skip {print}
  ' "$COMMANDS_FILE" > "$tmp"

  {
    echo "[$model]"
    printf '%s\n' "${commands[@]}"
    echo
  } >> "$tmp"

  mv "$tmp" "$COMMANDS_FILE"

  echo -e "${GREEN}型號修改成功${NC}"
  pause_return
}

########################################
# setting.csv 設定修改
########################################

edit_settings() {
  ensure_settings_file
  load_settings

  print_section "系統設定"

  echo "直接 Enter 表示不修改"
  echo

  read -rp "REBOOT_WAIT ($REBOOT_WAIT): " new_reboot
  read -rp "TELNET_CONNECT_TIMEOUT ($TELNET_CONNECT_TIMEOUT): " new_telnet_connect
  read -rp "TELNET_LOGIN_TIMEOUT ($TELNET_LOGIN_TIMEOUT): " new_telnet_login
  read -rp "PING_INTERVAL ($PING_INTERVAL): " new_ping_interval
  read -rp "同步更新數量 PARALLEL_UPDATE_LIMIT ($PARALLEL_UPDATE_LIMIT) [2-20]: " new_parallel

  REBOOT_WAIT="${new_reboot:-$REBOOT_WAIT}"
  TELNET_CONNECT_TIMEOUT="${new_telnet_connect:-$TELNET_CONNECT_TIMEOUT}"
  TELNET_LOGIN_TIMEOUT="${new_telnet_login:-$TELNET_LOGIN_TIMEOUT}"
  PING_INTERVAL="${new_ping_interval:-$PING_INTERVAL}"
  PARALLEL_UPDATE_LIMIT="${new_parallel:-$PARALLEL_UPDATE_LIMIT}"

  clamp_parallel_limit

  cat > "$SETTINGS_FILE" <<EOF
key,value
REBOOT_WAIT,$REBOOT_WAIT
TELNET_CONNECT_TIMEOUT,$TELNET_CONNECT_TIMEOUT
TELNET_LOGIN_TIMEOUT,$TELNET_LOGIN_TIMEOUT
PING_INTERVAL,$PING_INTERVAL
PARALLEL_UPDATE_LIMIT,$PARALLEL_UPDATE_LIMIT
EOF

  echo -e "${GREEN}setting.csv 已更新${NC}"
  pause_return
}

########################################
# OTA 管理
########################################

is_new_gateway_model() {
  local model="$1"

  case "$model" in
    M2_POE|M3|M200|M300)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

parse_ota_status() {
  local raw="$1"
  local clean

  clean="$(printf '%s\n' "$raw" | tr -d '\r')"

  echo "$clean"

  if echo "$clean" | grep -qx "__OTA_LOCKED__"; then
    return 0
  fi

  if echo "$clean" | grep -qx "__OTA_UNLOCKED__"; then
    return 1
  fi

  return 2
}

run_ota_action_on_device() {

  local host="$1"
  local port="$2"
  local user="$3"
  local model="$4"
  local location="$5"
  local name="$6"
  local action="$7"
  local output=""

  print_section "OTA 管理 - ${name}"

  echo "名稱   : $name"
  echo "位置   : $location"
  echo "IP     : $host"
  echo "PORT   : $port"
  echo "帳號   : $user"
  echo "型號   : $model"
  echo "動作   : $action"
  echo

  if is_private_ipv4 "$host"; then
    if ! ping_once "$host"; then
      echo -e "${RED}設備離線${NC}"
      pause_return
      return
    fi
  fi

  echo -e "${BLUE}開始執行 OTA ${action}...${NC}"
  echo

  ########################################
  # 新網關：M2_POE / M3 / M200 / M300
  ########################################

  if is_new_gateway_model "$model"; then

    case "$action" in

      check)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      lock)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "chattr +i /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      unlock)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "chattr -i /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      *)
        echo "未知動作: $action"
        pause_return
        return
      ;;
    esac

  ########################################
  # 舊網關：M2 / H1 / M1S
  ########################################

  else

    case "$action" in

      check)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "cd /tmp && wget -O /tmp/curl \\"http://master.dl.sourceforge.net/project/mgl03/bin/curl?viasf=1\\" && chmod a+x /tmp/curl\r"
expect "# "

send "/tmp/curl -s -k -L -o /tmp/busybox-mipsel https://github.com/niceboygithub/AqaraM1SM2fw/raw/main/binutils/busybox-mipsel\r"
expect "# "

send "chmod a+x /tmp/busybox-mipsel\r"
expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "/tmp/busybox-mipsel lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      lock)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "cd /tmp && wget -O /tmp/curl \\"http://master.dl.sourceforge.net/project/mgl03/bin/curl?viasf=1\\" && chmod a+x /tmp/curl\r"
expect "# "

send "/tmp/curl -s -k -L -o /tmp/busybox-mipsel https://github.com/niceboygithub/AqaraM1SM2fw/raw/main/binutils/busybox-mipsel\r"
expect "# "

send "chmod a+x /tmp/busybox-mipsel\r"
expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "/tmp/busybox-mipsel chattr +i /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "/tmp/busybox-mipsel lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      unlock)
        output=$("$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT
spawn $TELNET_BIN $host $port

expect {
  "login:" { send "$user\r" }
  "username:" { send "$user\r" }
}

expect "# "

send "cd /tmp && wget -O /tmp/curl \\"http://master.dl.sourceforge.net/project/mgl03/bin/curl?viasf=1\\" && chmod a+x /tmp/curl\r"
expect "# "

send "/tmp/curl -s -k -L -o /tmp/busybox-mipsel https://github.com/niceboygithub/AqaraM1SM2fw/raw/main/binutils/busybox-mipsel\r"
expect "# "

send "chmod a+x /tmp/busybox-mipsel\r"
expect "# "

send "mkdir -p /data/ota_dir\r"
expect "# "

send "\[ -e /data/ota_dir/lumi_fw.tar \] || touch /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "/tmp/busybox-mipsel chattr -i /data/ota_dir/lumi_fw.tar\r"
expect "# "

send "/tmp/busybox-mipsel lsattr /data/ota_dir/lumi_fw.tar 2>/dev/null | tee /tmp/ota_lsattr.out\r"
expect "# "

send "cut -c6 /tmp/ota_lsattr.out | grep -q i && echo __OTA_LOCKED__ || echo __OTA_UNLOCKED__\r"
expect "# "

send "exit\r"
expect eof
EOF
)
      ;;

      *)
        echo "未知動作: $action"
        pause_return
        return
      ;;
    esac

  fi

  echo
  parse_ota_status "$output" >/dev/null
  case $? in
    0)
      echo -e "${GREEN}OTA 狀態：已鎖定${NC}"
      ;;
    1)
      echo -e "${YELLOW}OTA 狀態：未鎖定${NC}"
      ;;
    *)
      echo -e "${RED}OTA 狀態：無法判斷${NC}"
      ;;
  esac

  echo
  echo -e "${GREEN}OTA ${action} 執行完成${NC}"
  pause_return
}

select_device_for_ota_action() {

  local action="$1"

  if [[ ! -f "$CSV_FILE" ]]; then
    print_section "OTA 管理"
    echo "devices.csv 不存在"
    pause_return
    return
  fi

  load_all_devices

  print_section "OTA 管理 - 選擇設備"

  if [[ ${#ALL_IPS[@]} -eq 0 ]]; then
    echo "目前沒有設備資料"
    pause_return
    return
  fi

  local i=0
  while [[ $i -lt ${#ALL_IPS[@]} ]]; do
    echo "$((i+1)). ${ALL_NAMES[$i]} | ${ALL_LOCATIONS[$i]} | $(model_display "${ALL_MODELS[$i]}") | ${ALL_IPS[$i]}"
    i=$((i+1))
  done

  echo "0. 返回"

  read -rp "請選擇要操作的網關: " choice
  [[ "$choice" == "0" ]] && return

  local idx=$((choice-1))

  if [[ $idx -lt 0 || $idx -ge ${#ALL_IPS[@]} ]]; then
    echo "選項錯誤"
    pause_return
    return
  fi

  run_ota_action_on_device \
    "${ALL_IPS[$idx]}" \
    "${ALL_PORTS[$idx]}" \
    "${ALL_USERS[$idx]}" \
    "${ALL_MODELS[$idx]}" \
    "${ALL_LOCATIONS[$idx]}" \
    "${ALL_NAMES[$idx]}" \
    "$action"
}

ota_manage_menu() {

  while true; do

    print_section "OTA 管理"

    echo "1. 檢查 OTA 鎖定"
    echo "2. 鎖定 OTA 更新"
    echo "3. 解除 OTA 鎖定"
    echo "0. 返回"

    read -rp "選擇: " opt

    case "$opt" in
      1)
        select_device_for_ota_action "check"
      ;;
      2)
        select_device_for_ota_action "lock"
      ;;
      3)
        select_device_for_ota_action "unlock"
      ;;
      0)
        return
      ;;
      *)
        echo "無效選項"
        pause_return
      ;;
    esac

  done
}

########################################
# Telnet 更新核心
########################################

run_telnet_update() {

  local host="$1"
  local port="$2"
  local user="$3"
  local model="$4"
  local location="$5"
  local name="$6"
  local result_dir="$7"
  local result_key="$8"
  local live_mode="$9"

  local cmd
  cmd=$(get_update_cmd "$model")

  local safe
  safe="$(safe_filename "$name")"

  local log="${LOG_DIR}/${safe}.log"
  : > "$log"

  ########################################
  # 找不到更新指令
  ########################################

  if [[ -z "$cmd" ]]; then
    if [[ "$live_mode" == "1" ]]; then
      echo -e "${RED}[$name] 找不到更新指令${NC}"
    fi

    printf 'FAIL|%s|%s|找不到更新指令|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
    return 1
  fi

  ########################################
  # LOG HEADER
  ########################################

  {
    echo "=================================================="
    echo "名稱   : $name"
    echo "位置   : $location"
    echo "IP     : $host"
    echo "PORT   : $port"
    echo "帳號   : $user"
    echo "型號   : $model"
    echo "開始時間: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "=================================================="
  } >> "$log"

  ########################################
  # 單顆更新 (Live mode)
  ########################################

  if [[ "$live_mode" == "1" ]]; then

    echo
    echo -e "${BLUE}==================================================${NC}"
    echo "名稱   : $name"
    echo "位置   : $location"
    echo "IP     : $host"
    echo "PORT   : $port"
    echo "帳號   : $user"
    echo "型號   : $model"
    echo "LOG    : ./logs/${safe}.log"
    echo -e "${BLUE}==================================================${NC}"

    ########################################
    # ping
    ########################################

    if is_private_ipv4 "$host"; then
      if ! ping_once "$host"; then
        echo -e "${RED}[$name] 網關離線${NC}"
        echo "[$name] 網關離線" >> "$log"

        printf 'FAIL|%s|%s|網關離線|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
        return 1
      fi

      echo -e "${GREEN}[$name] Ping 正常，開始 telnet...${NC}"
      echo "[$name] Ping 正常" >> "$log"
    else
      echo -e "${YELLOW}[$name] 非私網位址，略過 ping${NC}"
      echo "[$name] 非私網位址" >> "$log"
    fi

    ########################################
    # telnet expect
    ########################################

    "$EXPECT_BIN" <<EOF | tee -a "$log"
set timeout $TELNET_LOGIN_TIMEOUT

spawn $TELNET_BIN $host $port

expect {
  "login:" {
      send "$user\r"
  }
  "username:" {
      send "$user\r"
  }
}

expect "# "

send "sh\r"

expect "# "

$(echo "$cmd" | while read -r line; do
  esc=$(printf '%s\n' "$line" | sed 's/"/\\"/g')
  echo "send -- \"$esc\r\""
done)

expect eof
EOF

    ########################################
    # reboot
    ########################################

    echo -e "${YELLOW}[$name] reboot 已送出，等待 ${REBOOT_WAIT} 秒...${NC}"
    echo "[$name] reboot 已送出" >> "$log"

    if wait_for_reconnect "$host" "$port"; then
      echo -e "${GREEN}[$name] 設備已重新上線${NC}"
      echo "[$name] 設備已重新上線" >> "$log"
    else
      echo -e "${RED}[$name] 設備未重新連線${NC}"
      echo "[$name] 設備未重新連線" >> "$log"

      printf 'FAIL|%s|%s|設備未重新連線|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
      return 1
    fi

    ########################################
    # 檢查更新結果
    ########################################

    if grep -q "===Update ALL Success===" "$log"; then

      local version
      version=$(get_updated_version "$log")
      [[ -z "$version" ]] && version="未知版本"

      echo -e "${GREEN}[$name] 更新成功，目前版本為 ${version}${NC}"
      echo "[$name] 更新成功 ${version}" >> "$log"

      printf 'SUCCESS|%s|%s|%s|%s\n' "$name" "$host" "$version" "$log" > "${result_dir}/${result_key}.result"
      return 0

    else

      echo -e "${RED}[$name] 未偵測到更新成功${NC}"
      echo "[$name] 未偵測到更新成功" >> "$log"

      printf 'FAIL|%s|%s|未偵測到更新成功|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
      return 1

    fi
  fi

  ########################################
  # 多顆更新 (Quiet mode)
  ########################################

  {

    if is_private_ipv4 "$host"; then
      if ! ping_once "$host"; then
        echo "[$name] 網關離線"
        printf 'FAIL|%s|%s|網關離線|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
        exit 1
      fi
    fi

    "$EXPECT_BIN" <<EOF
set timeout $TELNET_LOGIN_TIMEOUT

spawn $TELNET_BIN $host $port

expect {
  "login:" {
      send "$user\r"
  }
  "username:" {
      send "$user\r"
  }
}

expect "# "

send "sh\r"

expect "# "

$(echo "$cmd" | while read -r line; do
  esc=$(printf '%s\n' "$line" | sed 's/"/\\"/g')
  echo "send -- \"$esc\r\""
done)

expect eof
EOF

    if ! wait_for_reconnect "$host" "$port" 1; then
      echo "[$name] 設備未重新連線"
      printf 'FAIL|%s|%s|設備未重新連線|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
      exit 1
    fi

    if grep -q "===Update ALL Success===" "$log"; then
      local version
      version=$(get_updated_version "$log")
      [[ -z "$version" ]] && version="未知版本"

      echo "[$name] 更新成功 ${version}"
      printf 'SUCCESS|%s|%s|%s|%s\n' "$name" "$host" "$version" "$log" > "${result_dir}/${result_key}.result"
      exit 0
    else
      echo "[$name] 未偵測到更新成功"
      printf 'FAIL|%s|%s|未偵測到更新成功|%s\n' "$name" "$host" "$log" > "${result_dir}/${result_key}.result"
      exit 1
    fi

  } >> "$log" 2>&1
}
########################################
# 同步更新控制
########################################

perform_updates() {

  SUCCESS_LIST=()
  FAIL_LIST=()

  ########################################
  # 單顆更新判斷
  ########################################

  local live_mode=0
  if [[ ${#DEVICE_IPS[@]} -eq 1 ]]; then
    live_mode=1
  fi

  local i
  local running=0
  local result_dir
  result_dir="$(mktemp -d)"

  ########################################
  # 啟動更新
  ########################################

  for ((i=0;i<${#DEVICE_IPS[@]};i++)); do

    local result_key
    result_key=$(printf "%04d" "$i")

    if [[ "$live_mode" == "0" ]]; then
      printf "[%d/%d] 已啟動更新：%s (%s)\n" \
        "$((i+1))" "${#DEVICE_IPS[@]}" \
        "${DEVICE_NAMES[$i]}" "${DEVICE_IPS[$i]}"
    fi

    ########################################
    # 單顆更新
    ########################################

    if [[ "$live_mode" == "1" ]]; then

      run_telnet_update \
        "${DEVICE_IPS[$i]}" \
        "${DEVICE_PORTS[$i]}" \
        "${DEVICE_USERS[$i]}" \
        "${DEVICE_MODELS[$i]}" \
        "${DEVICE_LOCATIONS[$i]}" \
        "${DEVICE_NAMES[$i]}" \
        "$result_dir" \
        "$result_key" \
        "$live_mode"

    ########################################
    # 多顆更新
    ########################################

    else

      run_telnet_update \
        "${DEVICE_IPS[$i]}" \
        "${DEVICE_PORTS[$i]}" \
        "${DEVICE_USERS[$i]}" \
        "${DEVICE_MODELS[$i]}" \
        "${DEVICE_LOCATIONS[$i]}" \
        "${DEVICE_NAMES[$i]}" \
        "$result_dir" \
        "$result_key" \
        "$live_mode" &

      ((running++))

      ########################################
      # 控制最大同步數
      ########################################

      if [[ $running -ge $PARALLEL_UPDATE_LIMIT ]]; then
        wait -n
        ((running--))
      fi
    fi

  done

  ########################################
  # 等待剩餘任務
  ########################################

  wait

  ########################################
  # 結果統計
  ########################################

  local success_count=0
  local fail_count=0

  for file in "$result_dir"/*.result; do

    [[ -e "$file" ]] || continue

    local status name ip extra log_path
    IFS='|' read -r status name ip extra log_path < "$file"

    if [[ "$status" == "SUCCESS" ]]; then
      ((success_count++))
    else
      ((fail_count++))
    fi

  done

  ########################################
  # 結果摘要
  ########################################

  echo
  printf "\r\033[K"
  echo "================ 更新完成 ================"
  echo

  ########################################
  # 成功列表
  ########################################

  echo "成功 (${success_count}):"

  local idx=1

  for file in $(ls "$result_dir"/*.result 2>/dev/null | sort); do

    local status name ip extra log_path
    IFS='|' read -r status name ip extra log_path < "$file"

    if [[ "$status" == "SUCCESS" ]]; then

      local short_log="./logs/$(basename "$log_path")"

      printf "%d. %s\n" "$idx" "$name"
      printf "   IP      : %s\n" "$ip"
      printf "   Version : %s\n" "$extra"
      printf "   Log     : %s\n\n" "$short_log"

      ((idx++))
    fi

  done

  ########################################
  # 失敗列表
  ########################################

  echo "失敗 (${fail_count}):"

  idx=1

  for file in $(ls "$result_dir"/*.result 2>/dev/null | sort); do

    local status name ip extra log_path
    IFS='|' read -r status name ip extra log_path < "$file"

    if [[ "$status" == "FAIL" ]]; then

      local short_log="./logs/$(basename "$log_path")"

      printf "%d. %s\n" "$idx" "$name"
      printf "   IP      : %s\n" "$ip"
      printf "   Reason  : %s\n" "$extra"
      printf "   Log     : %s\n\n" "$short_log"

      ((idx++))
    fi

  done

  echo "=========================================="

  rm -rf "$result_dir"

  pause_return
}
########################################
# 更新網關選單
########################################

update_gateway_menu() {

  while true; do

    load_locations

    print_section "你想要更新哪個網關？"

    echo "1. 全部"

    local i
    for ((i=0;i<${#LOCATIONS[@]};i++)); do
      echo "$((i+2)). ${LOCATIONS[$i]}"
    done

    echo "0. 返回"

    read -rp "請輸入選項: " opt

    [[ "$opt" == "0" ]] && return

    ########################################
    # 全部
    ########################################

    if [[ "$opt" == "1" ]]; then

      load_all_devices

      DEVICE_IPS=("${ALL_IPS[@]}")
      DEVICE_PORTS=("${ALL_PORTS[@]}")
      DEVICE_USERS=("${ALL_USERS[@]}")
      DEVICE_MODELS=("${ALL_MODELS[@]}")
      DEVICE_LOCATIONS=("${ALL_LOCATIONS[@]}")
      DEVICE_NAMES=("${ALL_NAMES[@]}")

      perform_updates
      continue
    fi

    ########################################
    # location
    ########################################

    local idx=$((opt-2))

    if [[ $idx -ge 0 && $idx -lt ${#LOCATIONS[@]} ]]; then

      local location="${LOCATIONS[$idx]}"

      while true; do

        ########################################
        # 每次回到這層重新讀 devices.csv
        ########################################

        load_devices_by_location "$location"

        print_section "$location 有哪些網關要更新？"

        if [[ ${#DEVICE_IPS[@]} -eq 0 ]]; then
          echo "目前沒有設備資料"
          echo
          echo "0. 返回上層"

          read -rp "請輸入選項: " sub
          [[ "$sub" == "0" ]] && break

          continue
        fi

        echo "1. 全部 $location"

        local j
        for ((j=0;j<${#DEVICE_IPS[@]};j++)); do
          echo "$((j+2)). ${DEVICE_NAMES[$j]} ($(model_display "${DEVICE_MODELS[$j]}"))"
        done

        echo "0. 返回上層"

        read -rp "請輸入選項: " sub

        [[ "$sub" == "0" ]] && break

        ########################################
        # location 全部
        ########################################

        if [[ "$sub" == "1" ]]; then
          perform_updates
          continue
        fi

        ########################################
        # 單台
        ########################################

        local d=$((sub-2))

        if [[ $d -ge 0 && $d -lt ${#DEVICE_IPS[@]} ]]; then

          local selected_ip="${DEVICE_IPS[$d]}"
          local selected_port="${DEVICE_PORTS[$d]}"
          local selected_user="${DEVICE_USERS[$d]}"
          local selected_model="${DEVICE_MODELS[$d]}"
          local selected_location="${DEVICE_LOCATIONS[$d]}"
          local selected_name="${DEVICE_NAMES[$d]}"

          DEVICE_IPS=("$selected_ip")
          DEVICE_PORTS=("$selected_port")
          DEVICE_USERS=("$selected_user")
          DEVICE_MODELS=("$selected_model")
          DEVICE_LOCATIONS=("$selected_location")
          DEVICE_NAMES=("$selected_name")

          perform_updates
        fi

      done
    fi
  done
}

########################################
# 設定選單
########################################

settings_menu() {

  while true; do

    print_section "設定"

    echo "1. 查看"
    echo "2. 新增"
    echo "3. 修改"
    echo "4. 刪除"
    echo "5. 系統設定"
    echo "0. 返回"

    read -rp "選擇: " opt

    case "$opt" in

      1)

        print_section "查看"

        echo "1. 查看設備"
        echo "2. 查看網關型號與指令"
        echo "3. 查看系統設定"
        echo "0. 返回"

        read -rp "選擇: " sub

        case "$sub" in
          1) view_devices ;;
          2) view_models ;;
          3) view_settings ;;
        esac
      ;;

      2)

        print_section "新增"

        echo "1. 新增設備"
        echo "2. 新增網關型號與更新指令"
        echo "0. 返回"

        read -rp "選擇: " sub

        case "$sub" in
          1) add_device ;;
          2) add_model ;;
        esac
      ;;

      3)

        print_section "修改"

        echo "1. 修改設備"
        echo "2. 修改網關型號與指令"
        echo "0. 返回"

        read -rp "選擇: " sub

        case "$sub" in
          1) edit_device ;;
          2) edit_model ;;
        esac
      ;;

      4)

        print_section "刪除"

        echo "1. 刪除設備"
        echo "2. 刪除網關型號與指令"
        echo "0. 返回"

        read -rp "選擇: " sub

        case "$sub" in
          1) delete_device ;;
          2) delete_model ;;
        esac
      ;;

      5)
        edit_settings
      ;;

      0)
        return
      ;;

    esac

  done
}

########################################
# 主選單
########################################

main_menu() {

  while true; do

    print_header

    show_missing_files_notice

    echo

    if can_update_gateways; then

      echo "1. 更新網關"
      echo "2. OTA 管理"
      echo "3. 設定"
      echo "0. 離開"

      read -rp "請輸入選項: " choice

      case "$choice" in

        1)
          update_gateway_menu
        ;;

        2)
          ota_manage_menu
        ;;

        3)
          settings_menu
        ;;

        0)
          echo "已離開"
          exit 0
        ;;

      esac

    else

      echo "1. 設定"
      echo "0. 離開"

      read -rp "請輸入選項: " choice

      case "$choice" in

        1)
          settings_menu
        ;;

        0)
          echo "已離開"
          exit 0
        ;;

      esac

    fi

  done
}

########################################
# main
########################################

main() {

  detect_os
  check_requirements

  ensure_settings_file
  ensure_log_dir

  load_settings

  main_menu
}

main
