#!/bin/sh
# routevia — Passwall installer for stock OpenWrt
# https://github.com/NoSleep-bot/routevia
#
# Usage: wget -qO- https://data.justownit.ru/routevia/install.sh | sh
#        or: sh install.sh

set -e

# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

LOGFILE="/tmp/routevia.log"
STATEFILE="/etc/routevia.conf"
REPO_BASE="https://data.justownit.ru/routevia/configs"

# --- Helpers ---
log()   { printf "  ${GREEN}[+]${NC} %s\n" "$1"; echo "[+] $1" >> "$LOGFILE"; }
warn()  { printf "  ${YELLOW}[!]${NC} %s\n" "$1"; echo "[!] $1" >> "$LOGFILE"; }
die()   { printf "  ${RED}[x]${NC} %s\n" "$1"; echo "[x] $1" >> "$LOGFILE"; exit 1; }

run() {
    echo ">>> $*" >> "$LOGFILE"
    if ! "$@" >> "$LOGFILE" 2>&1; then
        tail -20 "$LOGFILE" >&2
        return 1
    fi
}

# --- Progress bar ---
TOTAL_STEPS=1
CURRENT_STEP=0

progress_init() { TOTAL_STEPS=$1; CURRENT_STEP=0; }

show_progress() {
    pct=$((CURRENT_STEP * 100 / TOTAL_STEPS))
    filled=$((pct / 5))
    bar=""
    i=0
    while [ $i -lt 20 ]; do
        if [ $i -lt $filled ]; then bar="${bar}#"; else bar="${bar}-"; fi
        i=$((i + 1))
    done
    printf "\r  [${GREEN}${bar}${NC}] %d%%" "$pct"
}

step() {
    CURRENT_STEP=$((CURRENT_STEP + 1))
    printf "\r\033[K${GREEN}[+]${NC} %s\n" "$1"
    echo "[+] $1" >> "$LOGFILE"
    show_progress
}

step_log() {
    printf "\r\033[K    %s\n" "$1"
    echo "    $1" >> "$LOGFILE"
    show_progress
}

clear_progress() { printf "\r\033[K"; }

# --- Pre-flight checks ---
[ "$(id -u)" -eq 0 ] || die "Run as root: sudo sh install.sh"
command -v opkg >/dev/null 2>&1 || die "opkg not found"

if [ -f /etc/openwrt_release ]; then
    . /etc/openwrt_release
else
    die "/etc/openwrt_release not found"
fi

RELEASE="${DISTRIB_RELEASE:-unknown}"
ARCH=$(opkg print-architecture | awk '{print $2}' | grep -v "^all$" | grep -v "^noarch$" | tail -1)
[ -z "$ARCH" ] && die "Cannot detect architecture"

# --- State file helpers ---
state_read() {
    # Read a key from state file ($1 = key name)
    if [ -f "$STATEFILE" ]; then
        grep "^${1}=" "$STATEFILE" 2>/dev/null | head -1 | cut -d'=' -f2-
    fi
}

state_write() {
    # Write a key=value to state file ($1 = key, $2 = value)
    if [ -f "$STATEFILE" ] && grep -q "^${1}=" "$STATEFILE" 2>/dev/null; then
        sed -i "s/^${1}=.*/${1}=${2}/" "$STATEFILE"
    else
        echo "${1}=${2}" >> "$STATEFILE"
    fi
}

# --- Detect current state ---
PW_INSTALLED=""
PW_CURRENT_VER=""
CURRENT_MODE=""

detect_state() {
    if opkg list-installed 2>/dev/null | grep -q "luci-app-passwall"; then
        PW_INSTALLED="yes"
        PW_CURRENT_VER=$(opkg list-installed 2>/dev/null | grep "luci-app-passwall" | head -1 | awk '{print $3}')
    fi
    CURRENT_MODE=$(state_read MODE)
    CURRENT_MODE="${CURRENT_MODE:-unknown}"
}

# --- Version comparison (BusyBox-compatible, no sort -V) ---
# Returns 0 if $1 > $2
version_gt() {
    _v1_major=$(echo "$1" | cut -d. -f1)
    _v1_minor=$(echo "$1" | cut -d. -f2)
    _v1_patch=$(echo "$1" | cut -d. -f3)
    _v2_major=$(echo "$2" | cut -d. -f1)
    _v2_minor=$(echo "$2" | cut -d. -f2)
    _v2_patch=$(echo "$2" | cut -d. -f3)

    [ "$_v1_major" -gt "$_v2_major" ] 2>/dev/null && return 0
    [ "$_v1_major" -lt "$_v2_major" ] 2>/dev/null && return 1
    [ "$_v1_minor" -gt "$_v2_minor" ] 2>/dev/null && return 0
    [ "$_v1_minor" -lt "$_v2_minor" ] 2>/dev/null && return 1
    [ "$_v1_patch" -gt "$_v2_patch" ] 2>/dev/null && return 0
    return 1
}

# Fetch latest config version from versions.txt
# Sets TARGET_VER
get_latest_config_version() {
    VERSIONS_URL="${REPO_BASE}/versions.txt"
    VERSIONS_FILE="/tmp/routevia-versions.txt"

    wget -qO "$VERSIONS_FILE" "$VERSIONS_URL" 2>/dev/null || die "Cannot fetch versions.txt"
    [ -s "$VERSIONS_FILE" ] || die "versions.txt is empty"

    TARGET_VER=""
    while IFS= read -r _line; do
        _line=$(echo "$_line" | sed 's/\r//g; s/ //g')
        [ -z "$_line" ] && continue
        if [ -z "$TARGET_VER" ]; then
            TARGET_VER="$_line"
        elif version_gt "$_line" "$TARGET_VER"; then
            TARGET_VER="$_line"
        fi
    done < "$VERSIONS_FILE"
    rm -f "$VERSIONS_FILE"

    [ -z "$TARGET_VER" ] && die "No versions available"
    echo "Latest config version: ${TARGET_VER}" >> "$LOGFILE"
}

# Load geodata URLs from geodata.conf ($1 = version)
# Sets GEOIP_PROXY, GEOSITE_PROXY, GEOIP_DIRECT, GEOSITE_DIRECT
load_geodata_conf() {
    _ver=$1
    GEODATA_URL="${REPO_BASE}/${_ver}/geodata.conf"
    GEODATA_FILE="/tmp/geodata.conf"

    wget -qO "$GEODATA_FILE" "$GEODATA_URL" 2>/dev/null || die "geodata.conf not found for ${_ver}"
    [ -s "$GEODATA_FILE" ] || die "geodata.conf is empty for ${_ver}"

    # Reset before sourcing
    GEOIP_PROXY=""; GEOSITE_PROXY=""; GEOIP_DIRECT=""; GEOSITE_DIRECT=""
    . "$GEODATA_FILE"
    rm -f "$GEODATA_FILE"

    [ -z "$GEOIP_PROXY" ] && die "Incomplete geodata.conf: missing GEOIP_PROXY"
    [ -z "$GEOSITE_PROXY" ] && die "Incomplete geodata.conf: missing GEOSITE_PROXY"
    [ -z "$GEOIP_DIRECT" ] && die "Incomplete geodata.conf: missing GEOIP_DIRECT"
    [ -z "$GEOSITE_DIRECT" ] && die "Incomplete geodata.conf: missing GEOSITE_DIRECT"

    echo "Loaded geodata.conf for ${_ver}" >> "$LOGFILE"
}

# Apply geodata for a given mode ($1 = version, $2 = proxy|direct)
apply_geodata() {
    _ver=$1
    _mode=$2
    GEODIR="/usr/share/v2ray"
    mkdir -p "$GEODIR"

    load_geodata_conf "$_ver"

    if [ "$_mode" = "proxy" ]; then
        _geoip_url="$GEOIP_PROXY"
        _geosite_url="$GEOSITE_PROXY"
    else
        _geoip_url="$GEOIP_DIRECT"
        _geosite_url="$GEOSITE_DIRECT"
    fi

    wget -qO "$GEODIR/geoip.dat" "$_geoip_url" || return 1
    echo "Downloaded: $_geoip_url" >> "$LOGFILE"
    wget -qO "$GEODIR/geosite.dat" "$_geosite_url" || return 1
    echo "Downloaded: $_geosite_url" >> "$LOGFILE"
    return 0
}

# Save subscription URL+remark before config overwrite
save_subscription() {
    _SUB_URL=""
    _SUB_REMARK=""
    _SUB_URL=$(uci get passwall.@subscribe_list[0].url 2>/dev/null) || true
    _SUB_REMARK=$(uci get passwall.@subscribe_list[0].remark 2>/dev/null) || true
    if [ -n "$_SUB_URL" ]; then
        echo "Saved subscription: ${_SUB_REMARK} (${_SUB_URL})" >> "$LOGFILE"
    fi
}

# Restore subscription and fetch nodes via subscribe.lua
restore_subscription() {
    [ -z "$_SUB_URL" ] && return 0

    echo "Restoring subscription: ${_SUB_REMARK}..." >> "$LOGFILE"

    uci add passwall subscribe_list >> "$LOGFILE" 2>&1
    uci set "passwall.@subscribe_list[-1].url=${_SUB_URL}"
    uci set "passwall.@subscribe_list[-1].remark=${_SUB_REMARK}"
    uci commit passwall >> "$LOGFILE" 2>&1

    # Fetch nodes from subscription
    if [ -f /usr/share/passwall/subscribe.lua ]; then
        echo "Fetching nodes from subscription..." >> "$LOGFILE"
        lua /usr/share/passwall/subscribe.lua start @subscribe_list[0] manual >> "$LOGFILE" 2>&1 || {
            warn "subscribe.lua failed — subscription saved, update nodes manually from LuCI"
        }
    else
        warn "subscribe.lua not found — subscription saved, update nodes manually from LuCI"
    fi

    return 0
}

# Apply config + geodata for a given mode ($1 = version, $2 = proxy|direct)
apply_mode() {
    _ver=$1
    _mode=$2
    CONFIG_URL="${REPO_BASE}/${_ver}/${_mode}.tar.gz"
    CONFIG_FILE="/tmp/passwall-config.tar.gz"

    echo "Config URL: ${CONFIG_URL}" >> "$LOGFILE"

    if ! wget -qO "$CONFIG_FILE" "$CONFIG_URL" 2>/dev/null || ! [ -s "$CONFIG_FILE" ]; then
        rm -f "$CONFIG_FILE"
        return 1
    fi

    # Save subscription before overwriting config
    save_subscription

    # Stop Passwall before replacing config (no-op if already stopped)
    /etc/init.d/passwall stop >> "$LOGFILE" 2>&1 || true

    tar xzf "$CONFIG_FILE" -C / >> "$LOGFILE" 2>&1 || { rm -f "$CONFIG_FILE"; return 1; }
    rm -f "$CONFIG_FILE"

    apply_geodata "$_ver" "$_mode" || return 1

    # Restore subscription + fetch nodes
    restore_subscription

    state_write MODE "$_mode"
    CURRENT_MODE="$_mode"
    return 0
}

detect_state

# --- SourceForge / GitHub patterns ---
case "$RELEASE" in
    *SNAPSHOT*|*snapshot*) SF_PATH="snapshots" ;;
    *)
        BRANCH=$(echo "$RELEASE" | sed 's/\([0-9]*\.[0-9]*\).*/\1/')
        SF_PATH="releases/packages-${BRANCH}"
        ;;
esac

SF_BASE="https://downloads.sourceforge.net/project/openwrt-passwall-build/${SF_PATH}/${ARCH}"

case "$BRANCH" in
    25.*)  IPK_PATTERN="25.12+_luci-app-passwall"; IPK_EXT=".apk" ;;
    23.*|24.*) IPK_PATTERN="23.05-24.10_luci-app-passwall"; IPK_EXT=".ipk" ;;
    *)     IPK_PATTERN="22.03-_luci-app-passwall"; IPK_EXT=".ipk" ;;
esac

# =====================================================================
# FUNCTION: Install Passwall
# =====================================================================
do_install() {
    if [ "$PW_INSTALLED" = "yes" ]; then
        echo ""
        printf "  ${GREEN}Passwall already installed (${PW_CURRENT_VER})${NC}\n"
        echo ""
        return
    fi

    : > "$LOGFILE"

    # Determine target config version
    get_latest_config_version
    echo ""
    printf "${YELLOW}"
    echo "  ========================================================"
    echo "  НЕ ОТКЛЮЧАЙТЕ ПИТАНИЕ И ИНТЕРНЕТ ВО ВРЕМЯ УСТАНОВКИ!"
    echo "  ПРЕРЫВАНИЕ МОЖЕТ ОСТАВИТЬ СИСТЕМУ В НЕРАБОЧЕМ СОСТОЯНИИ."
    echo "  ========================================================"
    printf "${NC}"
    echo ""
    printf "  Continue? [y/N] "
    read -r confirm < /dev/tty
    case "$confirm" in y|Y) ;; *) echo "  Cancelled."; return ;; esac
    echo ""

    # Ask subscription URL upfront (before long install process)
    printf "  Subscription URL (Enter to skip): "
    read -r _input_sub_url < /dev/tty
    _input_sub_url=$(echo "$_input_sub_url" | sed 's/[[:space:]]//g')
    if [ -n "$_input_sub_url" ]; then
        _input_sub_remark=$(echo "$_input_sub_url" | sed 's|.*://||' | sed 's|/.*||' | awk -F. '{
            n = NF
            if (n >= 2) print $(n-1)
            else print $1
        }')
        [ -z "$_input_sub_remark" ] && _input_sub_remark="subscription"
        printf "  Subscription: ${GREEN}%s${NC} (%s)\n" "$_input_sub_remark" "$_input_sub_url"
    else
        _input_sub_url=""
        printf "  Subscription: ${YELLOW}skipped${NC}\n"
    fi
    echo ""

    # Check free space
    FREE_KB=$(df /overlay 2>/dev/null | awk 'NR==2{print $4}' || echo "0")
    if [ "$FREE_KB" -lt 4096 ] 2>/dev/null; then
        warn "Low space on /overlay: ${FREE_KB}KB (need ~4MB+)"
        printf "  Continue? [y/N] "
        read -r ans < /dev/tty
        case "$ans" in y|Y) ;; *) echo "  Cancelled."; return ;; esac
    fi

    progress_init 13

    # 1. Feeds
    step "Feeds: SourceForge passwall-build..."
    FEEDS_FILE="/etc/opkg/customfeeds.conf"
    [ -f "$FEEDS_FILE" ] && sed -i '/passwall/d' "$FEEDS_FILE"
    cat >> "$FEEDS_FILE" <<EOF
src/gz passwall_packages ${SF_BASE}/passwall_packages
src/gz passwall_luci ${SF_BASE}/passwall_luci
src/gz passwall2 ${SF_BASE}/passwall2
EOF
    step_log "3 feeds added"

    # 2. GPG key
    step "GPG key..."
    KEYFILE="/tmp/passwall.pub"
    KEY_OK=0
    if wget -qO "$KEYFILE" "https://downloads.sourceforge.net/project/openwrt-passwall-build/passwall.pub" 2>/dev/null; then
        if [ -s "$KEYFILE" ] && opkg-key add "$KEYFILE" >> "$LOGFILE" 2>&1; then
            KEY_OK=1
            step_log "Key added"
        fi
    fi
    rm -f "$KEYFILE"
    if [ "$KEY_OK" -eq 0 ]; then
        step_log "Key unavailable, signature check disabled"
        if ! grep -q "check_signature" /etc/opkg.conf 2>/dev/null; then
            echo "option check_signature 0" >> /etc/opkg.conf
        else
            sed -i 's/option check_signature.*/option check_signature 0/' /etc/opkg.conf
        fi
    fi

    # 3. Update lists
    step "Updating package lists..."
    if ! run opkg update; then die "opkg update failed (see ${LOGFILE})"; fi
    if ! grep -q "passwall" "$LOGFILE"; then die "Passwall feeds not found (see ${LOGFILE})"; fi
    step_log "Done"

    # 4. Dependencies
    step "Installing dependencies (xray-core, geoview)..."
    ATTEMPT=0; MAX_ATTEMPTS=3; DEP_OK=0
    while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do
        ATTEMPT=$((ATTEMPT + 1))
        if run opkg install xray-core geoview; then DEP_OK=1; break; fi
        if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
            warn "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}, retry in 5s..."; sleep 5
        fi
    done
    [ "$DEP_OK" -eq 1 ] || die "Dependencies install failed (see ${LOGFILE})"
    step_log "xray-core + geoview installed"

    # 5. Geodata
    step "Downloading geodata (geoip + geosite)..."
    apply_geodata "$TARGET_VER" "proxy" || die "Failed to download geodata"
    GEOIP_SIZE=$(ls -l /usr/share/v2ray/geoip.dat 2>/dev/null | awk '{print $5}')
    GEOSITE_SIZE=$(ls -l /usr/share/v2ray/geosite.dat 2>/dev/null | awk '{print $5}')
    step_log "geoip.dat (${GEOIP_SIZE}B) + geosite.dat (${GEOSITE_SIZE}B)"

    # 6. Fetch latest version
    step "Fetching latest Passwall from GitHub..."
    GH_API="https://api.github.com/repos/Openwrt-Passwall/openwrt-passwall/releases/latest"
    GH_JSON="/tmp/pw_release.json"
    wget -qO "$GH_JSON" "$GH_API" || die "GitHub API request failed"
    sed -i 's/,"/,\n"/g' "$GH_JSON" 2>/dev/null || true
    PW_VERSION=$(grep '"tag_name"' "$GH_JSON" | head -1 | sed 's/.*"tag_name"[^"]*"\([^"]*\)".*/\1/')
    [ -z "$PW_VERSION" ] && die "Cannot determine Passwall version"
    IPK_URL=$(grep '"browser_download_url"' "$GH_JSON" | grep "${IPK_PATTERN}" | grep -v "zh-cn" | head -1 | sed 's/.*"browser_download_url"[^"]*"\([^"]*\)".*/\1/')
    [ -z "$IPK_URL" ] && die "Asset '${IPK_PATTERN}' not found in ${PW_VERSION}"
    rm -f "$GH_JSON"
    step_log "Latest: ${PW_VERSION}"

    # 7. Download
    step "Downloading luci-app-passwall..."
    IPK_FILE="/tmp/luci-app-passwall${IPK_EXT}"
    wget -qO "$IPK_FILE" "$IPK_URL" || die "Download failed"
    step_log "Done"

    # 8. Install
    step "Installing luci-app-passwall..."
    if ! run opkg install "$IPK_FILE"; then die "luci-app-passwall install failed (see ${LOGFILE})"; fi
    rm -f "$IPK_FILE"
    step_log "luci-app-passwall ${PW_VERSION}"

    # 9. Apply config
    step "Applying config (proxy mode)..."
    CONFIG_URL="${REPO_BASE}/${TARGET_VER}/proxy.tar.gz"
    CONFIG_FILE="/tmp/passwall-config.tar.gz"
    echo "Config URL: ${CONFIG_URL}" >> "$LOGFILE"
    wget -qO "$CONFIG_FILE" "$CONFIG_URL" 2>/dev/null || die "Config not found for ${TARGET_VER}"
    [ -s "$CONFIG_FILE" ] || die "Config is empty for ${TARGET_VER}"
    tar xzf "$CONFIG_FILE" -C / >> "$LOGFILE" 2>&1 || die "Failed to extract config"
    rm -f "$CONFIG_FILE"
    state_write MODE "proxy"
    state_write VERSION "$TARGET_VER"
    step_log "Config applied: ${TARGET_VER}/proxy"

    # 10. Subscription (apply if provided earlier)
    step "Subscription setup..."
    if [ -n "$_input_sub_url" ]; then
        uci add passwall subscribe_list >> "$LOGFILE" 2>&1
        uci set "passwall.@subscribe_list[-1].url=${_input_sub_url}"
        uci set "passwall.@subscribe_list[-1].remark=${_input_sub_remark}"
        uci commit passwall >> "$LOGFILE" 2>&1

        echo "Subscription added: ${_input_sub_remark} (${_input_sub_url})" >> "$LOGFILE"

        if [ -f /usr/share/passwall/subscribe.lua ]; then
            lua /usr/share/passwall/subscribe.lua start @subscribe_list[0] manual >> "$LOGFILE" 2>&1 || {
                warn "Node fetch failed — update manually from LuCI"
            }
        fi
        step_log "${_input_sub_remark}: nodes fetched"
    else
        step_log "Skipped"
    fi

    # 11. Verify
    step "Verifying..."
    PW_COUNT=$(opkg list-installed 2>/dev/null | grep -c "passwall" || true)
    [ "$PW_COUNT" -gt 0 ] && step_log "Passwall: ${PW_COUNT} package(s)" || die "Passwall not found after install"
    command -v xray >/dev/null 2>&1 && step_log "Xray: $(xray version 2>/dev/null | head -1)" || warn "xray not in PATH"
    command -v geoview >/dev/null 2>&1 && step_log "Geoview: OK" || warn "geoview not in PATH"

    # 12. Restart web server
    step "Restarting web server..."
    /etc/init.d/uhttpd restart >> "$LOGFILE" 2>&1 || /etc/init.d/nginx restart >> "$LOGFILE" 2>&1 || warn "Could not restart web server"
    step_log "Done"

    # 13. Save log
    step "Saving log..."
    PERSISTENT_LOG="/etc/routevia-install.log"
    cp "$LOGFILE" "$PERSISTENT_LOG" 2>/dev/null || PERSISTENT_LOG="$LOGFILE"
    step_log "Log: ${PERSISTENT_LOG}"

    clear_progress

    LAN_IP=$(uci get network.lan.ipaddr 2>/dev/null || echo "192.168.1.1")
    echo ""
    printf "${GREEN}"
    echo "  ========================================="
    echo "    Passwall ${PW_VERSION}"
    echo "    Xray + Geoview"
    echo "    LuCI: http://${LAN_IP}"
    echo "    Services -> PassWall"
    echo "  ========================================="
    printf "${NC}"
    echo ""
    printf "${YELLOW}"
    echo "  ========================================================"
    echo "  РОУТЕР БУДЕТ ПЕРЕЗАГРУЖЕН ЧЕРЕЗ 5 СЕКУНД"
    echo "  ========================================================"
    printf "${NC}"
    echo ""
    sleep 5
    reboot
}

# =====================================================================
# FUNCTION: Update Passwall
# =====================================================================
do_update() {
    if [ "$PW_INSTALLED" != "yes" ]; then
        echo ""
        printf "  ${RED}Passwall is not installed. Install first.${NC}\n"
        echo ""
        return
    fi

    : > "$LOGFILE"

    # Current config version from state file (fallback to opkg version)
    CURRENT_VER=$(state_read VERSION)
    if [ -z "$CURRENT_VER" ]; then
        CURRENT_VER=$(echo "$PW_CURRENT_VER" | sed 's/-.*//')
        warn "VERSION not in state file, using Passwall version: ${CURRENT_VER}"
    fi

    # Check for newer config version
    get_latest_config_version

    if ! version_gt "$TARGET_VER" "$CURRENT_VER"; then
        echo ""
        printf "  ${GREEN}Up to date (${CURRENT_VER})${NC}\n"
        echo ""
        return
    fi

    echo ""
    printf "  Update available: ${YELLOW}%s${NC} → ${GREEN}%s${NC}\n" "$CURRENT_VER" "$TARGET_VER"
    echo ""
    printf "  Continue? [y/N] "
    read -r confirm < /dev/tty
    case "$confirm" in y|Y) ;; *) echo "  Cancelled."; return ;; esac
    echo ""

    CURRENT_MODE=$(state_read MODE)
    CURRENT_MODE="${CURRENT_MODE:-proxy}"

    progress_init 8

    # 1. Stop Passwall
    step "Stopping Passwall..."
    /etc/init.d/passwall stop >> "$LOGFILE" 2>&1 || true
    step_log "Done"

    # 2. Update package lists
    step "Updating package lists..."
    if ! run opkg update; then die "opkg update failed (see ${LOGFILE})"; fi
    step_log "Done"

    # 3. Upgrade dependencies
    step "Upgrading xray-core, geoview..."
    run opkg upgrade xray-core geoview >> "$LOGFILE" 2>&1 || warn "opkg upgrade failed, trying reinstall"
    step_log "Done"

    # 4. Fetch latest Passwall from GitHub
    step "Fetching latest Passwall from GitHub..."
    GH_API="https://api.github.com/repos/Openwrt-Passwall/openwrt-passwall/releases/latest"
    GH_JSON="/tmp/pw_release.json"
    wget -qO "$GH_JSON" "$GH_API" || die "GitHub API request failed"
    sed -i 's/,"/,\n"/g' "$GH_JSON" 2>/dev/null || true
    PW_VERSION=$(grep '"tag_name"' "$GH_JSON" | head -1 | sed 's/.*"tag_name"[^"]*"\([^"]*\)".*/\1/')
    [ -z "$PW_VERSION" ] && die "Cannot determine Passwall version"
    IPK_URL=$(grep '"browser_download_url"' "$GH_JSON" | grep "${IPK_PATTERN}" | grep -v "zh-cn" | head -1 | sed 's/.*"browser_download_url"[^"]*"\([^"]*\)".*/\1/')
    [ -z "$IPK_URL" ] && die "Asset '${IPK_PATTERN}' not found in ${PW_VERSION}"
    rm -f "$GH_JSON"
    step_log "Latest: ${PW_VERSION}"

    # 5. Download + install Passwall
    step "Installing luci-app-passwall..."
    IPK_FILE="/tmp/luci-app-passwall${IPK_EXT}"
    wget -qO "$IPK_FILE" "$IPK_URL" || die "Download failed"
    if ! run opkg install --force-reinstall "$IPK_FILE"; then die "luci-app-passwall install failed (see ${LOGFILE})"; fi
    rm -f "$IPK_FILE"
    step_log "luci-app-passwall ${PW_VERSION}"

    # 6. Apply config + geodata
    step "Applying config (${CURRENT_MODE} mode)..."
    if ! apply_mode "$TARGET_VER" "$CURRENT_MODE"; then
        die "Failed to apply config ${TARGET_VER}/${CURRENT_MODE}"
    fi
    state_write VERSION "$TARGET_VER"
    step_log "Config applied: ${TARGET_VER}/${CURRENT_MODE}"

    # 7. Start Passwall
    step "Starting Passwall..."
    /etc/init.d/passwall start >> "$LOGFILE" 2>&1 || warn "Passwall start failed"
    step_log "Done"

    # 8. Save log
    step "Saving log..."
    PERSISTENT_LOG="/etc/routevia-update.log"
    cp "$LOGFILE" "$PERSISTENT_LOG" 2>/dev/null || PERSISTENT_LOG="$LOGFILE"
    step_log "Log: ${PERSISTENT_LOG}"

    clear_progress

    echo ""
    printf "${GREEN}"
    echo "  ========================================="
    echo "    Updated: ${CURRENT_VER} → ${TARGET_VER}"
    echo "    Passwall: ${PW_VERSION}"
    command -v xray >/dev/null 2>&1 && echo "    Xray: $(xray version 2>/dev/null | head -1)"
    echo "    Mode: ${CURRENT_MODE}"
    echo "  ========================================="
    printf "${NC}"
    echo ""
}

# =====================================================================
# FUNCTION: Change Route Mode
# =====================================================================
do_change_mode() {
    if [ "$PW_INSTALLED" != "yes" ]; then
        echo ""
        printf "  ${RED}Passwall is not installed. Install first.${NC}\n"
        echo ""
        return
    fi

    echo ""
    if [ "$CURRENT_MODE" != "unknown" ]; then
        printf "  Current mode: ${GREEN}%s${NC}\n" "$CURRENT_MODE"
    else
        printf "  Current mode: ${YELLOW}not set${NC}\n"
    fi
    echo ""
    echo "  1. Default Proxy (recommended)"
    echo "  2. Default Direct"
    echo "  0. Back"
    echo ""
    printf "  Select [0-2]: "
    read -r mode_choice < /dev/tty

    case "$mode_choice" in
        1)
            if [ "$CURRENT_MODE" = "proxy" ]; then
                echo ""
                printf "  ${GREEN}Already in Proxy mode.${NC}\n"
                echo ""
                return
            fi

            : > "$LOGFILE"
            CONFIG_VER=$(state_read VERSION)
            [ -z "$CONFIG_VER" ] && CONFIG_VER=$(echo "$PW_CURRENT_VER" | sed 's/-.*//')

            echo ""
            log "Switching to Proxy mode..."
            log "Downloading config + geodata..."

            if apply_mode "$CONFIG_VER" "proxy"; then
                log "Mode changed to Proxy (Passwall stopped)"
                warn "Enable Passwall manually: Services -> PassWall -> Enable"
            else
                die "Failed to switch mode (config not found for ${CONFIG_VER})"
            fi
            echo ""
            ;;
        2)
            if [ "$CURRENT_MODE" = "direct" ]; then
                echo ""
                printf "  ${GREEN}Already in Direct mode.${NC}\n"
                echo ""
                return
            fi

            : > "$LOGFILE"
            CONFIG_VER=$(state_read VERSION)
            [ -z "$CONFIG_VER" ] && CONFIG_VER=$(echo "$PW_CURRENT_VER" | sed 's/-.*//')

            echo ""
            log "Switching to Direct mode..."
            log "Downloading config + geodata..."

            if apply_mode "$CONFIG_VER" "direct"; then
                log "Mode changed to Direct (Passwall stopped)"
                warn "Enable Passwall manually: Services -> PassWall -> Enable"
            else
                die "Failed to switch mode (config not found for ${CONFIG_VER})"
            fi
            echo ""
            ;;
        0|"")
            return
            ;;
        *)
            echo ""
            printf "  ${RED}Invalid option.${NC}\n"
            echo ""
            ;;
    esac
}

# =====================================================================
# MAIN MENU
# =====================================================================
DEVICE_NAME=$(cat /tmp/sysinfo/model 2>/dev/null || uci get system.@system[0].hostname 2>/dev/null || echo "OpenWrt")

echo ""
echo "  ${DEVICE_NAME}"
echo "  OpenWrt ${RELEASE}, ${ARCH}"

if [ "$PW_INSTALLED" = "yes" ]; then
    if [ "$CURRENT_MODE" != "unknown" ]; then
        printf "  Passwall: ${GREEN}%s${NC} | Mode: ${GREEN}%s${NC}\n" "$PW_CURRENT_VER" "$CURRENT_MODE"
    else
        printf "  Passwall: ${GREEN}%s${NC}\n" "$PW_CURRENT_VER"
    fi
fi

echo ""
echo "  1. Install PassWall"
echo "  2. Update PassWall"
echo "  3. Change Route Mode"
echo "  0. Exit"
echo ""
printf "  Select [0-3]: "
read -r choice < /dev/tty

case "$choice" in
    1) do_install ;;
    2) do_update ;;
    3) do_change_mode ;;
    0|"") echo "  Bye."; exit 0 ;;
    *) echo "  Invalid option."; exit 1 ;;
esac
