#!/bin/bash

# UniSASE Agent Pre-installation Script
# Executed during the installation process to check environment dependencies

# Environment Variables
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "${SCRIPT_DIR}/appenv.conf"

echo "Executing UniSASE Agent pre-installation checks..."

#==============================================================================
# Installation Lock Management
#==============================================================================

# Acquire installation lock to prevent concurrent installations
acquire_installation_lock() {
    local lock_file="${AGENT_INSTALLER_PAK_DIR}/.lock"
    local max_lock_age_seconds=600  # 10 minutes

    echo "Checking for concurrent installations..."

    # Ensure installer directory exists
    mkdir -p "${AGENT_INSTALLER_PAK_DIR}"

    # Try to create lock file atomically using noclobber
    # This prevents race conditions when multiple processes try to create the lock
    local lock_acquired=false
    (
        set -o noclobber
        date +%s > "${lock_file}"
    ) 2>/dev/null && lock_acquired=true

    if [ "${lock_acquired}" = true ]; then
        # Successfully acquired lock
        echo "  Installation lock acquired"
        return 0
    fi

    # Lock file already exists, check its age to detect stale locks
    # Note: We use timestamp instead of PID because preinstall and afterinstall are separate processes.
    # The lock lifecycle is: preinstall creates -> afterinstall removes.
    if [ -f "${lock_file}" ]; then
        local lock_timestamp
        local current_timestamp
        local lock_age

        lock_timestamp=$(cat "${lock_file}" 2>/dev/null)
        current_timestamp=$(date +%s)

        # Validate timestamp is a valid number (using regex pattern matching)
        if [[ -n "${lock_timestamp}" && "${lock_timestamp}" =~ ^[0-9]+$ ]]; then
            lock_age=$((current_timestamp - lock_timestamp))

            if [ ${lock_age} -gt ${max_lock_age_seconds} ]; then
                # Lock is older than max age, consider it stale
                echo "  Found stale installation lock (age: $((lock_age / 60)) minutes)"
                echo "  Removing stale lock and acquiring new lock..."
                rm -f "${lock_file}"

                # Retry acquiring lock
                (
                    set -o noclobber
                    date +%s > "${lock_file}"
                ) 2>/dev/null && lock_acquired=true

                if [ "${lock_acquired}" = true ]; then
                    echo "  Installation lock acquired"
                    return 0
                else
                    echo "Error: Failed to acquire lock after removing stale lock"
                    exit 1
                fi
            else
                # Lock exists and is recent, another installation is in progress
                echo "Error: Another installation is currently in progress"
                echo "The installation lock was created $((lock_age / 60)) minutes ago."
                echo ""
                echo "If you are certain no installation is running, you can:"
                echo "  1. Wait $((max_lock_age_seconds / 60)) minutes for automatic stale lock cleanup, or"
                echo "  2. Manually remove the lock: rm -f ${lock_file}"
                exit 1
            fi
        else
            # Invalid timestamp in lock file, treat as stale
            echo "  Found invalid lock file, removing and acquiring new lock..."
            rm -f "${lock_file}"

            (
                set -o noclobber
                date +%s > "${lock_file}"
            ) 2>/dev/null && lock_acquired=true

            if [ "${lock_acquired}" = true ]; then
                echo "  Installation lock acquired"
                return 0
            else
                echo "Error: Failed to acquire lock after removing invalid lock"
                exit 1
            fi
        fi
    else
        # Lock file doesn't exist but creation failed (race condition?)
        echo "Error: Failed to acquire installation lock (possible race condition)"
        exit 1
    fi
}

# Clean up temporary files on error exit
cleanup_on_error() {
    local exit_code=$?

    # Only clean up on error exit (non-zero exit code)
    # On normal exit, cleanup will be done by afterinstall
    if [ $exit_code -eq 0 ]; then
        return
    fi

    echo "Pre-installation failed, cleaning up temporary files..."

    # Clean up app.pak directory (includes lock file)
    if [ -d "${AGENT_INSTALLER_PAK_DIR}" ]; then
        echo "  Removing app.pak directory..."
        rm -rf "${AGENT_INSTALLER_PAK_DIR}"
    fi

    echo "Cleanup completed."
}

# Acquire installation lock before proceeding
acquire_installation_lock

# Set up trap to clean up temporary files on error exit
trap cleanup_on_error EXIT

#==============================================================================
# Process Management Functions
#==============================================================================

# Function to get agent process ID
get_agent_pid() {
    # Find running agent process, matching config file path
    pgrep -f "${AGENT_BIN}.*${AGENT_CONF_FILE}" 2>/dev/null | head -1
}

# Function to get monitor script process ID
get_monitor_pid() {
    # Find running monitor script process, matching script path
    pgrep -f "${MONITOR_SCRIPT}" 2>/dev/null | head -1
}

# Function to force kill a process
force_kill_process() {
    local pid=$1
    local process_name=$2

    if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
        echo "Terminating $process_name process (PID: $pid)..."

        # Send TERM signal first
        kill -TERM "$pid" 2>/dev/null

        # Wait for process to terminate
        local count=0
        while [ $count -lt 5 ] && kill -0 "$pid" 2>/dev/null; do
            sleep 1
            count=$((count + 1))
        done

        # If still not terminated, force kill
        if kill -0 "$pid" 2>/dev/null; then
            echo "Force killing $process_name process (PID: $pid)..."
            kill -KILL "$pid" 2>/dev/null
        fi

        echo "$process_name process terminated."
    fi
}

# Function to check PA version from panabit.inf
check_pa_version() {
    local panabit_info_file="/usr/ramdisk/etc/panabit.inf"
    # Minimum required PA version. ENVER encodes the minor in hex, so this must
    # be written the same way (e.g. r7p8 => "8.78", r7p12 => "8.7C").
    local min_pa_version="8.77"

    if [ ! -f "$panabit_info_file" ]; then
        echo "Error: Panabit info file not found: $panabit_info_file"
        echo "Please ensure this script is running on a supported PA system."
        exit 1
    fi

    # Extract ENVER=... line
    local enver_line
    enver_line=$(grep -i -E '^[[:space:]]*ENVER[[:space:]]*=' "$panabit_info_file" | head -n1 || true)
    if [ -z "$enver_line" ]; then
        echo "Error: PA version (ENVER) not found in $panabit_info_file"
        exit 1
    fi

    local enver_value
    enver_value=${enver_line#*=}

    # Strip inline comments starting with '#' or ';' (best-effort)
    enver_value=${enver_value%%#*}
    enver_value=${enver_value%%;*}

    # Trim spaces and surrounding quotes
    enver_value=$(echo "$enver_value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed 's/^\"//;s/\"$//')

    if [ -z "$enver_value" ]; then
        echo "Error: PA version (ENVER) is empty in $panabit_info_file"
        exit 1
    fi

    # Validate hexadecimal version format like X or X.Y (PA ENVER is major.minor,
    # e.g. r7p10 => "8.7A"). The minor is hex, so digits A-F are legal here; the
    # comparison below uses only major.minor, so no third component is accepted.
    if ! [[ "$enver_value" =~ ^[0-9A-Fa-f]+(\.[0-9A-Fa-f]+)?$ ]]; then
        echo "Error: Invalid PA version format in $panabit_info_file: $enver_value"
        exit 1
    fi

    # Convert versions to comparable integers. Each dot-separated component is a
    # hexadecimal number (the minor spans one byte, 0x00-0xFF), so parse with
    # base 16 and pack as major*256 + minor to keep the ordering monotonic
    # (e.g. r7p5 "8.75" => 0x75 sorts below r7p10 "8.7A" => 0x7A).
    local enver_num min_num
    local major minor

    IFS=. read -r major minor _ <<< "$enver_value"
    enver_num=$(( 16#${major:-0} * 256 + 16#${minor:-0} ))

    IFS=. read -r major minor _ <<< "$min_pa_version"
    min_num=$(( 16#${major:-0} * 256 + 16#${minor:-0} ))

    echo "Detected PA version: $enver_value (minimum required: $min_pa_version)"

    if [ "$enver_num" -lt "$min_num" ]; then
        echo "Error: PA version $enver_value is lower than required $min_pa_version."
        echo "Please upgrade PA before installing the UniSASE Agent."
        exit 1
    fi
}

# Check architecture
DEVICE_ARCH=""
ARCH=$(uname -m)
case $ARCH in
    x86_64|amd64)
        DEVICE_ARCH="amd64"
        ;;
    aarch64|arm64)
        DEVICE_ARCH="arm64"
        ;;
    *)
        echo "Error: Unsupported architecture: $ARCH"
        exit 1
        ;;
esac

# Verify package architecture matches device architecture
if [ -n "${APP_ARCH}" ]; then
    echo "Package architecture: ${APP_ARCH}"
    echo "Device architecture: ${DEVICE_ARCH}"

    if [ "${APP_ARCH}" != "${DEVICE_ARCH}" ]; then
        echo "Error: Architecture mismatch. Package is for ${APP_ARCH}, but device is ${DEVICE_ARCH}."
        echo "Please use the correct installation package for your device."
        exit 1
    else
        echo "Architecture verification passed: Package (${APP_ARCH}) matches device (${DEVICE_ARCH})"
    fi
else
    echo "Warning: Package architecture information not available. Skipping architecture verification."
    echo "This may indicate an older package format or a packaging issue."
fi

# Check PA version (ENVER) before installation
check_pa_version

# Check for xz tool (required for extracting compressed binaries)
echo "Checking for required tools..."
if ! command -v xz >/dev/null 2>&1; then
    echo "Error: 'xz' command not found. This is required for installation."
    echo "Please install xz-utils package."
    exit 1
fi
echo "  xz tool is available"

# Check disk space for installation root (PGPATH)
REQUIRED_SPACE=10  # 10MB
AVAILABLE_SPACE=$(df "$PGPATH" | awk 'NR==2 {print int($4/1024)}')
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
    echo "Error: Insufficient disk space. Requires at least ${REQUIRED_SPACE}MB, but only ${AVAILABLE_SPACE}MB is available."
    exit 1
fi

# Check free space on /tmp (or parent of AGENT_INSTALLER_PAK_DIR) for app.pak
TMP_PARENT_DIR=$(dirname "${AGENT_INSTALLER_PAK_DIR}")
APP_PAK_SOURCE="${SCRIPT_DIR}/app.pak"

# Default required space (MB) if we cannot determine app.pak size
TMP_REQUIRED_SPACE=30

if [ -f "${APP_PAK_SOURCE}" ]; then
    APP_PAK_SIZE_KB=$(du -k "${APP_PAK_SOURCE}" 2>/dev/null | awk 'NR==1 {print $1}')
    if [ -n "${APP_PAK_SIZE_KB}" ] && echo "${APP_PAK_SIZE_KB}" | grep -qE '^[0-9]+$'; then
        APP_PAK_SIZE_MB=$(( (APP_PAK_SIZE_KB + 1023) / 1024 ))
        # Add 10MB buffer on top of app.pak size
        TMP_REQUIRED_SPACE=$((APP_PAK_SIZE_MB + 10))
    fi
fi

TMP_AVAILABLE_SPACE=$(df "${TMP_PARENT_DIR}" 2>/dev/null | awk 'NR==2 {print int($4/1024)}')
if [ -n "${TMP_AVAILABLE_SPACE}" ]; then
    if [ "${TMP_AVAILABLE_SPACE}" -lt "${TMP_REQUIRED_SPACE}" ]; then
        echo "Error: Insufficient free space on ${TMP_PARENT_DIR}."
        echo "  Requires at least ${TMP_REQUIRED_SPACE}MB (app.pak + buffer),"
        echo "  but only ${TMP_AVAILABLE_SPACE}MB is available."
        exit 1
    else
        echo "  Free space on ${TMP_PARENT_DIR}: ${TMP_AVAILABLE_SPACE}MB (require >= ${TMP_REQUIRED_SPACE}MB)"
    fi
else
    echo "Warning: Failed to determine free space on ${TMP_PARENT_DIR}, skipping /tmp space check."
fi

# Process cleanup logic
echo "Checking for existing agent processes..."

# First, try to use appctrl if it exists
if [ -f "${RAM_APP_CTRL}" ] && [ -x "${RAM_APP_CTRL}" ]; then
    echo "Found appctrl script, attempting graceful shutdown..."
    "${RAM_APP_CTRL}" stop agent 2>/dev/null || true
    sleep 2
else
    echo "appctrl script not found, checking for running processes manually..."
fi

# Check if monitor process is still running and force kill if necessary
monitor_pid=$(get_monitor_pid)
if [ -n "$monitor_pid" ]; then
    echo "Found running monitor process, terminating..."
    force_kill_process "$monitor_pid" "monitor"
fi

# Check if agent process is still running and force kill if necessary
agent_pid=$(get_agent_pid)
if [ -n "$agent_pid" ]; then
    echo "Found running agent process, terminating..."
    force_kill_process "$agent_pid" "agent"
fi

echo "Process cleanup completed."

# Version check and service cleanup for ttyd and xrpc control scripts
# This ensures old version services are stopped before upgrading to new version.
# XRPC_CTRL_VERSION and TTYD_CTRL_VERSION are loaded from appenv.conf.
echo "Checking control script versions..."

# Flag to track if we need to stop remote-access module
need_stop_remote_access=false

# Check xrpc-ctl version
if [ -f "$XRPC_CTL_BIN" ] && [ -x "$XRPC_CTL_BIN" ]; then
    echo "  Found existing unisase-xrpc-ctl, checking version..."

    # Get current installed version
    current_xrpc_version=$("$XRPC_CTL_BIN" version --short 2>/dev/null || echo "")

    if [ -z "$current_xrpc_version" ]; then
        # Version command failed - old version without version command
        echo "    Warning: Failed to get xrpc-ctl version (old version without version command)"
        need_stop_remote_access=true
    else
        echo "    Current xrpc-ctl version: $current_xrpc_version"
        echo "    New xrpc-ctl version: $XRPC_CTRL_VERSION"

        # Compare versions
        if [ "$current_xrpc_version" != "$XRPC_CTRL_VERSION" ]; then
            echo "    Version mismatch detected"
            need_stop_remote_access=true
        else
            echo "    Version matches"
        fi
    fi
else
    echo "  unisase-xrpc-ctl not found or not executable, skipping version check"
fi

# Check ttyd-ctl version
if [ -f "$TTYD_CTL_BIN" ] && [ -x "$TTYD_CTL_BIN" ]; then
    echo "  Found existing unisase-ttyd-ctl, checking version..."

    # Get current installed version
    current_ttyd_version=$("$TTYD_CTL_BIN" version --short 2>/dev/null || echo "")

    if [ -z "$current_ttyd_version" ]; then
        # Version command failed - old version without version command
        echo "    Warning: Failed to get ttyd-ctl version (old version without version command)"
        need_stop_remote_access=true
    else
        echo "    Current ttyd-ctl version: $current_ttyd_version"
        echo "    New ttyd-ctl version: $TTYD_CTRL_VERSION"

        # Compare versions
        if [ "$current_ttyd_version" != "$TTYD_CTRL_VERSION" ]; then
            echo "    Version mismatch detected"
            need_stop_remote_access=true
        else
            echo "    Version matches"
        fi
    fi
else
    echo "  unisase-ttyd-ctl not found or not executable, skipping version check"
fi

# Stop remote-access module if version mismatch or old version detected
if [ "$need_stop_remote_access" = true ]; then
    echo "  Stopping remote-access module due to version mismatch or upgrade..."
    if [ -f "${RAM_APP_CTRL}" ] && [ -x "${RAM_APP_CTRL}" ]; then
        "${RAM_APP_CTRL}" stop remote-access 2>/dev/null || echo "    Warning: Failed to stop remote-access module (may not be running)"
    else
        echo "    Warning: appctrl not found, skipping remote-access module stop"
    fi
fi

echo "Control script version check completed."

# Remove existing agent debug client binary
if [ -f "${AGENT_BIN_DIR}/unisase-agent-debug-client" ]; then
    echo "Found existing agent debug client, removing..."
    rm -f "${AGENT_BIN_DIR}/unisase-agent-debug-client"
fi

# Move app.pak to /tmp to avoid copying to RAMDISK
# This significantly reduces RAMDISK space usage during installation
# Using /tmp for cross-platform compatibility (typically has 1777 permissions)
if [ -f "${SCRIPT_DIR}/app.pak" ]; then
    echo "Moving app.pak to /tmp to save RAMDISK space..."

    # Create installation directory in /tmp
    mkdir -p "${AGENT_INSTALLER_PAK_DIR}"

    # Copy app.pak to /tmp (use cp instead of mv for cross-filesystem compatibility)
    if cp "${SCRIPT_DIR}/app.pak" "${AGENT_INSTALLER_PAK_DIR}/app.pak"; then
        echo "  Successfully copied app.pak to ${AGENT_INSTALLER_PAK_DIR}/"
        # Remove original to prevent it from being copied to RAMDISK
        rm -f "${SCRIPT_DIR}/app.pak"
        echo "  Removed app.pak from source directory"
        echo "  This prevents app.pak from being copied to RAMDISK"
    else
        echo "Error: Failed to copy app.pak to ${AGENT_INSTALLER_PAK_DIR}"
        echo "This may cause RAMDISK space issues during installation."
        exit 1
    fi
else
    echo "Error: app.pak not found in installation package."
    echo "This installer requires the new compressed binary format (app.pak)."
    exit 1
fi

echo "Pre-installation checks completed."
exit 0
