#!/bin/bash
# ==============================================================================
# MeshDash R3.0 Installer Script
# Debian/Ubuntu/Raspberry Pi OS / Windows (WSL)
#
# MeshDash is Free and Open Source software, licensed under the GPLv3.
# For full license details, please see: https://www.gnu.org/licenses/gpl-3.0.html
#
# R3.0 CHANGES:
#   - Detects existing mesh-dash install, creates timestamped backup
#   - Migrates database and plugins from backup to new install
#   - Writes c2_installed.flag so dashboard knows it was C2-provisioned
#   - Slimmer account setup: API keys and endpoints now dashboard-configured
# ==============================================================================

set -o errexit
set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

# ------------------------- Dynamic URLs (Set by C2 Setup Wizard) -------------------------
MESHDASH_VERSION="{VERSION}"
API_KEY="{API_KEY}"
BASE_URL="{BASE_URL}"
OPERATOR_EMAIL="{OPERATOR_EMAIL}"
OPERATOR_FIRST_NAME="{OPERATOR_FIRST_NAME}"
OPERATOR_LAST_NAME="{OPERATOR_LAST_NAME}"
MESHTASTIC_CONNECTION_TYPE="{MESHTASTIC_CONNECTION_TYPE}"
MESHTASTIC_HOST="{MESHTASTIC_HOST}"
MESHTASTIC_PORT="{MESHTASTIC_PORT}"
MESHTASTIC_SERIAL_PORT="{MESHTASTIC_SERIAL_PORT}"
MESHTASTIC_BLE_MAC="{MESHTASTIC_BLE_MAC}"
WEBSERVER_PORT="{WEBSERVER_PORT}"
WEBSERVER_HOST="{WEBSERVER_HOST}"
AUTO_SERVICE="{AUTO_SERVICE}"

# --- [CRITICAL SAFEGUARD] PREVENT RAW TEMPLATE EXECUTION ---
if [[ "$BASE_URL" == \{* ]] || [[ "$API_KEY" == \{* ]]; then
    printf "\n${RED}${BOLD}[ERROR]${NC} This is a raw template script.\n"
    printf "You must generate your specific installation command using the Setup Wizard at:\n"
    printf "${CYAN}https://meshdash.co.uk/c2_setup.php${NC}\n\n"
    exit 1
fi
# --------------------------------------------------------------------------------------

ZIP_URL="$BASE_URL/versions/$MESHDASH_VERSION/mesh-dash.zip?r=$(date +%s)"
CONFIG_URL="$BASE_URL/user_setup_core.php?action=download_config&key=$API_KEY"

INSTALL_SUBDIR="mesh-dash"
PYTHON_CMD="python3"
MIN_PYTHON_VERSION="3.9"
VENV_DIR="mesh-dash_venv"
ZIP_FILE="mesh-dash.zip"
REQUIREMENTS_FILE="requirements.txt"
MAIN_SCRIPT="meshtastic_dashboard.py"
TOOLS_SCRIPT="mesh_dash_tools.sh"
CONFIG_FILE=".mesh-dash_config"
SERVICE_NAME="mesh-dash"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
RUN_SCRIPT_NAME="run_meshdash.sh"
SUMMARY_FILE="Installation_details.txt"
C2_FLAG_FILE="data/c2_installed.flag"
GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py"

SUDO_AVAILABLE=false
PYTHON_VERSION_DETECTED=""
OS_ID=""
OS_VERSION_ID=""
SCRIPT_RUN_DIR=""
INSTALLER_SCRIPT_PATH=""
BOX_WIDTH=70
MIGRATED_DB=false
MIGRATED_PLUGINS=false
BACKUP_DIR=""

# -------------------------------------------------------------------
# Utility functions
# -------------------------------------------------------------------
show_ascii_art() {
    printf "\n${CYAN}${BOLD}"
cat << "EOF"
 _  _  ____  ____  _  _  ____   __   ____  _  _
( \/ )(  __)/ ___)/ )( \(    \ / _\ / ___)/ )( \
/ \/ \ ) _) \___ \) __ ( ) D (/    \\___ \) __ (
\_)(_/(____)(____/\_)(_/(____/\_/\_/(____/\_)(_/

EOF
    printf "${NC}\n"
    sleep 1
}

echoinfo()  { printf "${BLUE}[INFO]${NC} %s\n" "$1"; }
echoerror() { printf "${RED}${BOLD}[ERROR]${NC} %s\n" "$@" >&2; }
echowarn()  { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
echostep()  { printf "\n\n${GREEN}${BOLD}=== %s ===${NC}\n" "$1"; }
echosubstep(){ printf "\n${CYAN}--- %s ---${NC}\n" "$1"; }
echook()    { printf "  ${GREEN}${BOLD}[OK]${NC} %s\n" "$1"; }

center_text() {
    local text="$1"
    local text_len_ansi
    text_len_ansi=$(echo -n "$text" | sed 's/\x1b\[[0-9;]*[mK]//g' | wc -c)
    local pad_len=$(( (BOX_WIDTH - 2 - text_len_ansi) / 2 ))
    [ $pad_len -lt 0 ] && pad_len=0
    local space_pad=$(printf '%*s' $pad_len '')
    local remaining_pad=$(( BOX_WIDTH - 2 - pad_len - text_len_ansi ))
    [ $remaining_pad -lt 0 ] && remaining_pad=0
    printf "#%s%s%*s#\n" "$space_pad" "$text" $remaining_pad ""
}

compare_versions() {
    local ver1=$1 ver2=$2
    if command -v dpkg &>/dev/null; then
        dpkg --compare-versions "$ver1" "ge" "$ver2" && return 0 || return 1
    elif command -v sort &>/dev/null && sort --version 2>&1 | grep -q 'GNU coreutils'; then
        [ "$(printf '%s\n%s\n' "$ver1" "$ver2" | sort -V | head -n1)" = "$ver2" ] && return 0 || return 1
    else
        local v1_major=$(echo "$ver1" | cut -d. -f1) v1_minor=$(echo "$ver1" | cut -d. -f2)
        local v2_major=$(echo "$ver2" | cut -d. -f1) v2_minor=$(echo "$ver2" | cut -d. -f2)
        [ "$v1_major" -gt "$v2_major" ] && return 0
        [ "$v1_major" -lt "$v2_major" ] && return 1
        [ "$v1_minor" -ge "$v2_minor" ] && return 0 || return 1
    fi
}

download_get_pip() {
    local target_dir="$1"
    local get_pip_path="${target_dir}/get-pip.py"
    echoinfo "Downloading get-pip.py..."
    if ! wget --progress=dot:giga -q "$GET_PIP_URL" -O "$get_pip_path"; then
        echoerror "Failed to download get-pip.py."
        rm -f "$get_pip_path"
        return 1
    fi
    echook "get-pip.py downloaded"
    return 0
}

# -------------------------------------------------------------------
# Prerequisite checks
# -------------------------------------------------------------------
check_command() {
    local cmd="$1" package_name="$2" optional="${3:-false}"
    echoinfo "Checking for: '$cmd'..."
    if command -v "$cmd" &>/dev/null; then
        if [ "$cmd" = "sudo" ]; then SUDO_AVAILABLE=true; fi
        if [[ "$cmd" == python* ]]; then
            local version_string=$("$cmd" --version 2>&1)
            if [[ "$version_string" =~ Python[[:space:]]+([0-9]+\.[0-9]+(\.[0-9]+)?) ]]; then
                if [ "$cmd" = "$PYTHON_CMD" ]; then PYTHON_VERSION_DETECTED="${BASH_REMATCH[1]}"; fi
            fi
        fi
        return 0
    fi

    if [ "$optional" = true ]; then
        echowarn "'$cmd' not found (optional). Package: $package_name"
        return 1
    fi

    echoerror "'$cmd' command not found. This is required."
    if [ "$cmd" = "sudo" ]; then
        echoerror "Cannot auto-install sudo. Please install it manually."
        exit 1
    fi

    if ! command -v sudo &>/dev/null; then
        echoerror "Automatic installation requires sudo."
        exit 1
    fi
    SUDO_AVAILABLE=true

    echoinfo "Auto-installing '$package_name'..."
    sudo apt update -qq >/dev/null 2>&1 || echowarn "apt update had issues, continuing..."
    sudo apt install -y -qq "$package_name" >/dev/null 2>&1 || { echoerror "Failed to install '$package_name'."; exit 1; }
    echook "'$package_name' installed"
    return 0
}

check_python_version() {
    if [ -z "$PYTHON_VERSION_DETECTED" ]; then
        if ! command -v "$PYTHON_CMD" &>/dev/null; then
            echoerror "Python '$PYTHON_CMD' not found."
            exit 1
        fi
        echowarn "Could not determine Python version — proceeding anyway"
        return 0
    fi

    if compare_versions "$PYTHON_VERSION_DETECTED" "$MIN_PYTHON_VERSION"; then
        echook "Python $PYTHON_VERSION_DETECTED >= $MIN_PYTHON_VERSION"
        return 0
    fi

    echoerror "Python $MIN_PYTHON_VERSION+ required — detected $PYTHON_VERSION_DETECTED"
    echoinfo "Attempting auto-upgrade..."

    if ! command -v sudo &>/dev/null; then SUDO_AVAILABLE=false; else SUDO_AVAILABLE=true; fi
    [ "$SUDO_AVAILABLE" = false ] && { echoerror "sudo not available — cannot upgrade Python."; exit 1; }

    sudo apt update -qq >/dev/null 2>&1
    local py_pkg="python${MIN_PYTHON_VERSION}" venv_pkg="python${MIN_PYTHON_VERSION}-venv"

    if ! apt-cache show "$py_pkg" &>/dev/null; then
        echowarn "Adding deadsnakes PPA for newer Python..."
        sudo apt-get install -y -qq software-properties-common >/dev/null 2>&1
        sudo add-apt-repository -y ppa:deadsnakes/ppa >/dev/null 2>&1
        sudo apt update -qq >/dev/null 2>&1
    fi

    sudo apt install -y "$py_pkg" "$venv_pkg" || { echoerror "Failed to install Python $MIN_PYTHON_VERSION."; exit 1; }
    PYTHON_CMD="/usr/bin/python${MIN_PYTHON_VERSION}"
    PYTHON_VERSION_DETECTED="$MIN_PYTHON_VERSION"
    echook "Python $MIN_PYTHON_VERSION installed"
    return 0
}

check_python_module() {
    local module_name="$1"
    echoinfo "Checking Python module: '$module_name'..."
    if ! $PYTHON_CMD -m "$module_name" --version &>/dev/null && \
       ! ( [ "$module_name" = "venv" ] && $PYTHON_CMD -m "$module_name" -h &>/dev/null ); then
        echowarn "Module '$module_name' missing — attempting install..."
        [ "$SUDO_AVAILABLE" = false ] && { echoerror "sudo required to install $module_name."; exit 1; }
        if [ "$module_name" = "pip" ]; then
            sudo apt install -y -qq python3-pip >/dev/null 2>&1 || $PYTHON_CMD -m ensurepip --upgrade >/dev/null 2>&1
        elif [ "$module_name" = "venv" ]; then
            sudo apt install -y -qq python3-venv "python${PYTHON_VERSION_DETECTED%.*}-venv" >/dev/null 2>&1
        fi
    fi
    echook "Module '$module_name' ready"
}

# -------------------------------------------------------------------
# OS detection
# -------------------------------------------------------------------
detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        OS_ID="${ID:-unknown}"
        OS_VERSION_ID="${VERSION_ID:-unknown}"
        echoinfo "Detected OS: ${PRETTY_NAME:-$OS_ID $OS_VERSION_ID}"
    else
        echowarn "/etc/os-release not found — assuming Debian-based"
        OS_ID="unknown"; OS_VERSION_ID="unknown"
    fi
}

# -------------------------------------------------------------------
# Smart backup detection
# -------------------------------------------------------------------
detect_existing_install() {
    local target="$SCRIPT_RUN_DIR/$INSTALL_SUBDIR"
    if [ -d "$target" ]; then
        local ts=$(date +%Y%m%d_%H%M%S)
        BACKUP_DIR="${target}_backup_${ts}"
        echoinfo "Existing 'mesh-dash' directory found at: $target"
        echoinfo "Will create backup at: ${BOLD}$BACKUP_DIR${NC}"
        return 0
    fi
    return 1
}

perform_backup() {
    if [ -z "$BACKUP_DIR" ]; then
        echoinfo "No existing install to back up."
        return 0
    fi

    echostep "Backing Up Existing Installation"
    echoinfo "Moving '$SCRIPT_RUN_DIR/$INSTALL_SUBDIR' → '$BACKUP_DIR'..."
    mv "$SCRIPT_RUN_DIR/$INSTALL_SUBDIR" "$BACKUP_DIR" || {
        echoerror "Failed to move existing install to backup location."
        exit 1
    }
    echook "Existing install backed up to: $BACKUP_DIR"

    # Check for database files in the backup
    if [ -f "$BACKUP_DIR/meshtastic_data.db" ] || \
       [ -f "$BACKUP_DIR/data/meshtastic_data.db" ] || \
       [ -f "$BACKUP_DIR/meshtastic_data_"*".db" ]; then
        echoinfo "Database files found in backup — will migrate after extraction."
    fi

    # Check for plugins
    if [ -d "$BACKUP_DIR/plugins" ] && [ "$(ls -A "$BACKUP_DIR/plugins" 2>/dev/null)" ]; then
        echoinfo "Existing plugins found — will migrate after extraction."
    fi
}

migrate_from_backup() {
    if [ -z "$BACKUP_DIR" ]; then return 0; fi

    local new_install="$SCRIPT_RUN_DIR/$INSTALL_SUBDIR"

    # ── 1. Migrate database files ──
    echostep "Migrating Database from Backup"

    # Find all .db files in backup root (legacy R2.x location)
    for db in "$BACKUP_DIR"/meshtastic_data*.db; do
        if [ -f "$db" ]; then
            local dbname=$(basename "$db")
            echoinfo "Migrating database: $dbname → $new_install/data/"
            mkdir -p "$new_install/data"
            cp "$db" "$new_install/data/$dbname"
            # Also copy WAL/SHM if they exist
            for ext in -wal -shm; do
                [ -f "${db}${ext}" ] && cp "${db}${ext}" "$new_install/data/${dbname}${ext}"
            done
            echook "$dbname migrated"
            MIGRATED_DB=true
        fi
    done

    # Find databases in backup/data/ (R2.2+ location)
    if [ -d "$BACKUP_DIR/data" ]; then
        for db in "$BACKUP_DIR/data"/*.db; do
            if [ -f "$db" ]; then
                local dbname=$(basename "$db")
                # Skip if already copied from root
                if [ ! -f "$new_install/data/$dbname" ]; then
                    echoinfo "Migrating database from data/: $dbname"
                    mkdir -p "$new_install/data"
                    cp "$db" "$new_install/data/$dbname"
                    for ext in -wal -shm; do
                        [ -f "${db}${ext}" ] && cp "${db}${ext}" "$new_install/data/${dbname}${ext}"
                    done
                    echook "$dbname migrated"
                    MIGRATED_DB=true
                fi
            fi
        done
        # Also migrate slots.json, tasks.db, users.db
        for f in slots.json tasks.db users.db geocode_cache.json; do
            if [ -f "$BACKUP_DIR/data/$f" ] && [ ! -f "$new_install/data/$f" ]; then
                echoinfo "Migrating: $f"
                cp "$BACKUP_DIR/data/$f" "$new_install/data/$f"
                echook "$f migrated"
            fi
        done
    fi

    # Also check for root-level tasks.db, slots.json
    for f in tasks.db slots.json users.db geocode_cache.json; do
        if [ -f "$BACKUP_DIR/$f" ] && [ ! -f "$new_install/data/$f" ]; then
            echoinfo "Migrating legacy file: $f"
            mkdir -p "$new_install/data"
            cp "$BACKUP_DIR/$f" "$new_install/data/$f"
            echook "$f migrated"
        fi
    done

    # Migrate R2.x slot config (.mesh-dash_slots.json → data/slots.json)
    if [ -f "$BACKUP_DIR/.mesh-dash_slots.json" ] && [ ! -f "$new_install/data/slots.json" ]; then
        echoinfo "Migrating legacy slot config: .mesh-dash_slots.json → data/slots.json"
        mkdir -p "$new_install/data"
        cp "$BACKUP_DIR/.mesh-dash_slots.json" "$new_install/data/slots.json"
        echook "Slot config migrated"
    fi

    # ── R2.x → R3.0 schema fix: add node_id column ──
    # Fresh R3.0 installs have node_id as PRIMARY KEY, but SQLite can't add
    # a PK via ALTER TABLE. We add it as a regular column and populate from num.
    if [ "$MIGRATED_DB" = true ]; then
        local main_db="$new_install/data/meshtastic_data.db"
        if [ -f "$main_db" ]; then
            echostep "Patching R2.x database schema (node_id)"
            # Add node_id column if missing (R2.x won't have it)
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN node_id TEXT" 2>/dev/null || true
            # Populate node_id from num column (int → hex !xxxxxxxx format)
            sqlite3 "$main_db" "UPDATE nodes SET node_id = '!' || printf('%08x', num) WHERE node_id IS NULL AND num IS NOT NULL" 2>/dev/null || true
            # Add index on node_id for migrated databases
            sqlite3 "$main_db" "CREATE UNIQUE INDEX IF NOT EXISTS idx_nodes_node_id ON nodes(node_id)" 2>/dev/null || true
            # Add other R3.0 columns that R2.x won't have
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN node_num INTEGER" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN long_name TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN short_name TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN macaddr TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN hw_model TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN firmware_version TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN role TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN is_local BOOLEAN DEFAULT FALSE" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN position_time INTEGER" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN telemetry_time INTEGER" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN user_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN position_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN device_metrics_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN environment_metrics_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN module_config_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN channel_info TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN created_at TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN updated_at TEXT" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN channel_utilization REAL" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE nodes ADD COLUMN air_util_tx REAL" 2>/dev/null || true
            # Populate node_num from num where missing
            sqlite3 "$main_db" "UPDATE nodes SET node_num = num WHERE node_num IS NULL AND num IS NOT NULL" 2>/dev/null || true
            # Add uniqueness index on node_num
            sqlite3 "$main_db" "CREATE UNIQUE INDEX IF NOT EXISTS idx_nodes_node_num ON nodes(node_num) WHERE node_num IS NOT NULL" 2>/dev/null || true
            # Add missing R3.0 columns to users table
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN role INTEGER DEFAULT 1" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN force_mfa BOOLEAN DEFAULT FALSE" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN must_setup_mfa BOOLEAN DEFAULT FALSE" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN totp_secret TEXT DEFAULT NULL" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN totp_enabled BOOLEAN DEFAULT FALSE" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN backup_codes TEXT DEFAULT NULL" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN last_login TEXT DEFAULT NULL" 2>/dev/null || true
            sqlite3 "$main_db" "ALTER TABLE users ADD COLUMN login_count INTEGER DEFAULT 0" 2>/dev/null || true
            echook "Database schema patched for R3.0"
        fi

        echook "Database migration complete"
    else
        echoinfo "No databases found to migrate (fresh install)"
    fi

    # ── 2. Migrate plugins ──
    echostep "Migrating Plugins from Backup"
    if [ -d "$BACKUP_DIR/plugins" ] && [ "$(ls -A "$BACKUP_DIR/plugins" 2>/dev/null)" ]; then
        echoinfo "Copying plugins from backup (preserving R3.0 bundled versions)..."
        mkdir -p "$new_install/plugins"
        local migrated=0
        local skipped=0
        for plugin_dir in "$BACKUP_DIR/plugins/"*/; do
            [ -d "$plugin_dir" ] || continue
            local pname=$(basename "$plugin_dir")
            if [ -d "$new_install/plugins/$pname" ]; then
                echoinfo "  Skipping '$pname' — R3.0 bundled version takes precedence"
                skipped=$((skipped + 1))
            else
                cp -r "$plugin_dir" "$new_install/plugins/$pname" 2>/dev/null || true
                migrated=$((migrated + 1))
            fi
        done
        local total=$(ls -1 "$new_install/plugins/" 2>/dev/null | wc -l)
        echook "$total plugin(s) available ($migrated migrated, $skipped R3.0 bundled preserved)"
        MIGRATED_PLUGINS=true

        # ── 2b. Migrate plugin config files from bundled plugins ──
        # For bundled plugins where R3.0 version takes precedence,
        # we still need to preserve user config/data files (databases, settings).
        local cfg_migrated=0
        for plugin_dir in "$BACKUP_DIR/plugins/"*/; do
            [ -d "$plugin_dir" ] || continue
            local pname=$(basename "$plugin_dir")
            local new_plugin_dir="$new_install/plugins/$pname"
            # Only process bundled plugins (where R3.0 version was kept)
            if [ -d "$new_plugin_dir" ]; then
                for cfg_ext in db json yaml yml conf csv ini toml; do
                    for cfg_file in "$plugin_dir"/*.$cfg_ext; do
                        [ -f "$cfg_file" ] || continue
                        local cfg_name=$(basename "$cfg_file")
                        # Don't overwrite R3.0's manifest.json or main.py
                        [ "$cfg_name" = "manifest.json" ] && continue
                        [ "$cfg_name" = "main.py" ] && continue
                        if [ ! -f "$new_plugin_dir/$cfg_name" ]; then
                            cp "$cfg_file" "$new_plugin_dir/$cfg_name" 2>/dev/null || true
                            cfg_migrated=$((cfg_migrated + 1))
                        else
                            # Config file exists in R3.0 — prefer user's version
                            cp "$cfg_file" "$new_plugin_dir/$cfg_name" 2>/dev/null || true
                            cfg_migrated=$((cfg_migrated + 1))
                        fi
                    done
                done
            fi
        done
        if [ $cfg_migrated -gt 0 ]; then
            echook "$cfg_migrated plugin config file(s) migrated from backup"
        fi
    else
        echoinfo "No plugins to migrate."
    fi

    # ── 3. Write migration record ──
    {
        echo "# MeshDash R3.0 Migration Record"
        echo "# Migrated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
        echo "# Backup directory: $BACKUP_DIR"
        echo "# DB migrated: $MIGRATED_DB"
        echo "# Plugins migrated: $MIGRATED_PLUGINS"
        echo "# Previous config preserved at: $BACKUP_DIR/$CONFIG_FILE"
    } > "$new_install/data/migration.log"
}

# -------------------------------------------------------------------
# Trap for exit
# -------------------------------------------------------------------
handle_exit() {
    local exit_code=$?
    if [ $exit_code -ne 0 ] && [[ $- == *e* ]]; then
        echoerror "Installation failed with exit code $exit_code."
    fi
}
trap handle_exit ERR

# -------------------------------------------------------------------
# Determine original user
# -------------------------------------------------------------------
ORIGINAL_USER=""
ORIGINAL_USER_PRIMARY_GROUP_ID=""
INSTALL_RUN_AS_ROOT=false
if [ -n "$SUDO_USER" ] && [ "$EUID" -eq 0 ]; then
    ORIGINAL_USER="$SUDO_USER"
    INSTALL_RUN_AS_ROOT=true
    ORIGINAL_USER_PRIMARY_GROUP_ID=$(id -g "$SUDO_USER" 2>/dev/null || echo "")
    echoinfo "Running via sudo as user: ${BOLD}$ORIGINAL_USER${NC}"
else
    ORIGINAL_USER=$(whoami)
    echoinfo "Running directly as: ${BOLD}$ORIGINAL_USER${NC}"
    ORIGINAL_USER_PRIMARY_GROUP_ID=$(id -g)
fi

# -------------------------------------------------------------------
# MAIN INSTALLATION FLOW
# -------------------------------------------------------------------
show_ascii_art
printf "\n${YELLOW}${BOLD}MeshDash R3.0 — Free & Open Source (GPLv3)${NC}\n"
printf "${YELLOW}https://meshdash.co.uk  |  https://github.com/rusjpmd/meshdash${NC}\n\n"
sleep 2

printf "${GREEN}${BOLD}##############################################################\n"
printf "#       MeshDash Installer — Version ${MESHDASH_VERSION}                     #\n"
printf "##############################################################${NC}\n\n"

detect_os

if [ -n "${BASH_SOURCE[0]}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
    INSTALLER_SCRIPT_PATH_ABS="$(readlink -f "${BASH_SOURCE[0]}")"
    SCRIPT_RUN_DIR="$(dirname "$INSTALLER_SCRIPT_PATH_ABS")"
    INSTALLER_SCRIPT_PATH="$INSTALLER_SCRIPT_PATH_ABS"
else
    SCRIPT_RUN_DIR="$(pwd)"
    INSTALLER_SCRIPT_PATH="$SCRIPT_RUN_DIR/install.sh"
    echowarn "Running via pipe — cannot self-delete after install."
fi

# ── Check for existing install and back up ──
detect_existing_install && perform_backup

# ── Create fresh install directory ──
INSTALL_DIR="$SCRIPT_RUN_DIR/$INSTALL_SUBDIR"
echoinfo "Install target: ${BOLD}$INSTALL_DIR${NC}"
mkdir -p "$INSTALL_DIR" || { echoerror "Failed to create $INSTALL_DIR."; exit 1; }
SUMMARY_FILE_PATH="$INSTALL_DIR/$SUMMARY_FILE"

# ── Prerequisite checks ──
echostep "Checking Prerequisites"
check_command "apt" "apt" false
check_command "sudo" "sudo" true
[ "$SUDO_AVAILABLE" = true ] && sudo apt update -qq >/dev/null 2>&1 || true
check_command "wget" "wget" false
check_command "unzip" "unzip" false
check_command "$PYTHON_CMD" "python3" false
check_python_version

# ── Compilation safeguards for ARM/RPi ──
if [ "$SUDO_AVAILABLE" = true ]; then
    echoinfo "Ensuring build tools are present (important for ARM/RPi)..."
    sudo apt install -y -qq build-essential python3-dev libxml2-dev libxslt-dev >/dev/null 2>&1 || true
fi

check_python_module "pip"
check_python_module "venv"

SYSTEMCTL_AVAILABLE=false
check_command "systemctl" "systemd" true && SYSTEMCTL_AVAILABLE=true

echoinfo "Prerequisites complete. Python: ${BOLD}$PYTHON_CMD${NC} (${PYTHON_VERSION_DETECTED:-unknown})"

# ── Download and extract ──
echostep "Downloading MeshDash R3.0"
ZIP_FILE_PATH="$INSTALL_DIR/$ZIP_FILE"
echoinfo "Downloading from: $ZIP_URL"
if ! wget --timeout=120 --progress=bar:force -c "$ZIP_URL" -O "$ZIP_FILE_PATH"; then
    echoerror "Download failed. Check network and URL."
    rm -f "$ZIP_FILE_PATH"
    exit 1
fi
echook "Download complete"

echostep "Extracting Application"
if ! unzip -o -q "$ZIP_FILE_PATH" -d "$INSTALL_DIR"; then
    echoerror "Failed to extract archive."
    exit 1
fi

# Verify extraction
MAIN_SCRIPT_PATH="$INSTALL_DIR/$MAIN_SCRIPT"
REQUIREMENTS_FILE_PATH="$INSTALL_DIR/$REQUIREMENTS_FILE"
[ ! -f "$MAIN_SCRIPT_PATH" ] && { echoerror "Main script missing after extraction: $MAIN_SCRIPT_PATH"; exit 1; }
[ ! -f "$REQUIREMENTS_FILE_PATH" ] && { echoerror "Requirements missing after extraction: $REQUIREMENTS_FILE_PATH"; exit 1; }
echook "Application extracted successfully"

# ── Migrate from backup BEFORE config download ──
migrate_from_backup

# ── Download configuration ──
echostep "Downloading Configuration"
CONFIG_FILE_PATH="$INSTALL_DIR/$CONFIG_FILE"
echoinfo "Fetching config from server..."
if ! wget --timeout=30 -q "$CONFIG_URL" -O "$CONFIG_FILE_PATH"; then
    echoerror "Failed to download config from server."
    exit 1
fi
echook "Configuration downloaded"

# ── Write C2 installation flag ──
echostep "Registering C2 Installation"
mkdir -p "$INSTALL_DIR/data"
cat > "$INSTALL_DIR/$C2_FLAG_FILE" <<- C2FLAG
# MeshDash C2 Installation Record
# This flag tells the dashboard it was provisioned via c2_setup.php
# Do not delete this file unless you want to re-run the setup wizard.

c2_version=$MESHDASH_VERSION
c2_installed_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
c2_api_key=$API_KEY
c2_operator_email=$OPERATOR_EMAIL
c2_operator_name=$OPERATOR_FIRST_NAME $OPERATOR_LAST_NAME
C2FLAG
echook "C2 installation flag written"

# ── Tools script permissions ──
TOOLS_SCRIPT_PATH="$INSTALL_DIR/$TOOLS_SCRIPT"
if [ -f "$TOOLS_SCRIPT_PATH" ]; then
    chmod +x "$TOOLS_SCRIPT_PATH"
    echook "Tools script permissions set"
fi

# ── Set up Python virtual environment ──
echostep "Setting Up Python Environment"
if [ -n "$VIRTUAL_ENV" ]; then
    echoinfo "Active virtual environment detected — using: $VIRTUAL_ENV"
    VENV_PATH="$VIRTUAL_ENV"
    PYTHON_EXEC="$VENV_PATH/bin/python"
else
    VENV_PATH="$INSTALL_DIR/$VENV_DIR"
    if [ -d "$VENV_PATH" ]; then
        echowarn "Removing existing venv..."
        rm -rf "$VENV_PATH"
    fi
    echoinfo "Creating virtual environment..."
    $PYTHON_CMD -m venv "$VENV_PATH" || { echoerror "Failed to create venv."; exit 1; }
    PYTHON_EXEC="$VENV_PATH/bin/python"
fi

# Ensure pip works
if ! "$PYTHON_EXEC" -m pip --version &>/dev/null; then
    "$PYTHON_EXEC" -m ensurepip --upgrade --default-pip || { echoerror "Failed to bootstrap pip."; exit 1; }
fi
echook "Python environment ready"

# ── Install dependencies ──
echostep "Installing Python Dependencies"
echoinfo "This may take several minutes on ARM/RPi devices..."

"$PYTHON_EXEC" -m pip install --upgrade pip -q || true

set +e
if ! "$PYTHON_EXEC" -m pip install --no-cache-dir --default-timeout=120 -r "$REQUIREMENTS_FILE_PATH"; then
    echowarn "Full install had issues — attempting core fallback..."
    "$PYTHON_EXEC" -m pip install --no-cache-dir --default-timeout=120 \
        uvicorn fastapi meshtastic sse-starlette \
        "passlib[bcrypt]" "bcrypt<4.1" python-multipart aiosqlite cryptography \
        python-jose[cryptography] beautifulsoup4 httpx jinja2 python-dotenv croniter \
        pyotp qrcode paho-mqtt meshcore pyserial || {
        echoerror "Core dependency install failed."
        exit 1
    }
fi
set -e
echook "Dependencies installed"

# ── Generate run script ──
echostep "Creating Run Script"
RUN_SCRIPT_PATH="$INSTALL_DIR/$RUN_SCRIPT_NAME"
cat > "$RUN_SCRIPT_PATH" <<- RUNEOF
#!/bin/bash
# MeshDash Run Script — Generated $(date)
SCRIPT_DIR="\$( cd "\$( dirname "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
VENV_PATH="$VENV_PATH"
PYTHON_EXEC="\$VENV_PATH/bin/python"
MAIN_APP="\$SCRIPT_DIR/$MAIN_SCRIPT"

[ ! -d "\$VENV_PATH" ] && { echo "ERROR: Venv not found at \$VENV_PATH"; exit 1; }
[ ! -x "\$PYTHON_EXEC" ] && { echo "ERROR: Python not executable: \$PYTHON_EXEC"; exit 1; }
[ ! -f "\$MAIN_APP" ] && { echo "ERROR: Main script not found: \$MAIN_APP"; exit 1; }

cd "\$SCRIPT_DIR" || exit 1
echo "Starting MeshDash $MESHDASH_VERSION..."
"\$PYTHON_EXEC" "\$MAIN_APP"
RUNEOF
chmod +x "$RUN_SCRIPT_PATH"
echook "Run script created"

# ── Fix ownership (if run via sudo) ──
if [ "$INSTALL_RUN_AS_ROOT" = true ] && [ -n "$ORIGINAL_USER" ]; then
    echostep "Fixing Directory Ownership"
    local owner_spec="$ORIGINAL_USER"
    [ -n "$ORIGINAL_USER_PRIMARY_GROUP_ID" ] && owner_spec="$ORIGINAL_USER:$ORIGINAL_USER_PRIMARY_GROUP_ID"
    chown -R "$owner_spec" "$INSTALL_DIR" 2>/dev/null || echowarn "Could not set ownership — you may need: sudo chown -R $owner_spec $INSTALL_DIR"
    echook "Ownership set to $owner_spec"
fi

# ── Systemd service setup ──
echostep "Systemd Service Setup"
SERVICE_SETUP_SUCCESSFUL="no"

if [ -f /.dockerenv ]; then
    echoinfo "Docker detected — skipping systemd."
else
    auto_service_lower=$(echo "$AUTO_SERVICE" | tr '[:upper:]' '[:lower:]' | xargs)
    [[ "$auto_service_lower" == "{auto_service}" ]] && auto_service_lower=""

    do_service=false
    if [[ "$auto_service_lower" == "yes" || "$auto_service_lower" == "true" || "$auto_service_lower" == "1" ]]; then
        do_service=true
        echoinfo "AUTO_SERVICE=yes — installing service automatically"
    elif [[ "$auto_service_lower" == "no" || "$auto_service_lower" == "false" || "$auto_service_lower" == "0" ]]; then
        echoinfo "AUTO_SERVICE=no — skipping service"
    elif [ "$SYSTEMCTL_AVAILABLE" = true ] && [ "$SUDO_AVAILABLE" = true ]; then
        read -p "Install MeshDash as a systemd service (auto-start on boot)? (y/N): " srv_resp
        [[ "$(echo "$srv_resp" | tr '[:upper:]' '[:lower:]')" =~ ^(y|yes)$ ]] && do_service=true
    fi

    if [ "$do_service" = true ] && [ "$SYSTEMCTL_AVAILABLE" = true ] && [ "$SUDO_AVAILABLE" = true ]; then
        SERVICE_USER_GROUP_NAME=$(id -gn "$ORIGINAL_USER" 2>/dev/null || echo "")

        sudo tee "$SERVICE_FILE" > /dev/null <<- SVCEOF
[Unit]
Description=MeshDash Service (v$MESHDASH_VERSION)
Documentation=https://meshdash.co.uk
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$ORIGINAL_USER
$([ -n "$SERVICE_USER_GROUP_NAME" ] && echo "Group=$SERVICE_USER_GROUP_NAME")
WorkingDirectory=$INSTALL_DIR
ExecStart=$PYTHON_EXEC $MAIN_SCRIPT_PATH
Restart=always
RestartSec=10
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
SVCEOF

        sudo chmod 644 "$SERVICE_FILE"
        sudo systemctl daemon-reload
        sudo systemctl enable "$SERVICE_NAME.service" 2>/dev/null || true
        sudo systemctl start "$SERVICE_NAME.service" 2>/dev/null || true

        sleep 2
        if sudo systemctl is-active --quiet "$SERVICE_NAME.service"; then
            echook "Service installed and running"
            SERVICE_SETUP_SUCCESSFUL="yes"
        else
            echowarn "Service installed but may not be running — check: sudo systemctl status $SERVICE_NAME"
        fi
    fi
fi

# ── Cleanup ──
echostep "Cleaning Up"
[ -f "$ZIP_FILE_PATH" ] && rm -f "$ZIP_FILE_PATH" && echoinfo "Archive removed"

if [ -n "$INSTALLER_SCRIPT_PATH" ] && [ -f "$INSTALLER_SCRIPT_PATH" ] && [[ "$INSTALLER_SCRIPT_PATH" != "/dev/fd/"* ]]; then
    rm -f "$INSTALLER_SCRIPT_PATH" 2>/dev/null && echoinfo "Installer script self-removed" || true
fi

# ── Summary ──
{
    BORDER=$(printf "%${BOX_WIDTH}s" | tr ' ' '=')
    printf "\n\n${GREEN}${BOLD}%s\n" "$BORDER"
    center_text "MeshDash R3.0 Installation Complete!"
    center_text "Version: ${MESHDASH_VERSION}"
    printf "%s${NC}\n" "$BORDER"
    printf "\n${BOLD}Install Details:${NC}\n"
    printf "  Location:     ${BOLD}%s${NC}\n" "$INSTALL_DIR"
    printf "  Running as:   ${BOLD}%s${NC}\n" "$ORIGINAL_USER"
    printf "  Python:       ${BOLD}%s${NC} (%s)\n" "$PYTHON_CMD" "${PYTHON_VERSION_DETECTED:-unknown}"
    printf "  Config:       ${BOLD}%s${NC}\n" "$CONFIG_FILE_PATH"

    if [ -n "$BACKUP_DIR" ]; then
        printf "\n${YELLOW}Backup created:${NC} ${BOLD}%s${NC}\n" "$BACKUP_DIR"
        [ "$MIGRATED_DB" = true ] && printf "  Database migrated from backup\n"
        [ "$MIGRATED_PLUGINS" = true ] && printf "  Plugins migrated from backup\n"
    fi

    printf "\n${BOLD}Access your dashboard:${NC}\n"
    printf "  ${CYAN}http://<device-ip>:%s${NC}\n" "${WEBSERVER_PORT:-8000}"

    if [ "$SERVICE_SETUP_SUCCESSFUL" = "yes" ]; then
        printf "\n  Service running. Manage with:\n"
        printf "  ${CYAN}sudo systemctl status/start/stop/restart %s${NC}\n" "$SERVICE_NAME"
    else
        printf "\n  Start manually:\n"
        printf "  ${CYAN}cd %s && ./%s${NC}\n" "$INSTALL_DIR" "$RUN_SCRIPT_NAME"
    fi

    printf "\n${YELLOW}All settings (API keys, C2 access, WebSerial) can be configured from the dashboard.${NC}\n\n"
} | tee "$SUMMARY_FILE_PATH"

exit 0
