#!/bin/bash

# UniSASE Agent Post-installation Script
# Executed after installation to perform complete configuration setup

echo "Executing UniSASE Agent post-installation configuration..."

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

# Global variables for device detection
USE_TMPFS=false
TMPFS_DIR=""
# Storage space threshold for tmpfs usage (in MB)
# If available space is less than this threshold, tmpfs will be used
TMPFS_SPACE_THRESHOLD_MB=1024  # 1GB

# Temporary extraction directory (using process ID to avoid conflicts)
# This directory will be created under /usr/unisase/run, which will be
# symlinked to tmpfs after setup_symlinks(), so extraction happens in tmpfs
AGENT_INSTALLER_TEMP_DIR="${UNISASE_RUNTIME_DIR}/unisase-agent-installer_$$"

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

# 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 cleanup() function
    if [ $exit_code -eq 0 ]; then
        return
    fi

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

    # Clean up temporary extraction directory
    if [ -d "${AGENT_INSTALLER_TEMP_DIR}" ]; then
        echo "  Removing temporary extraction directory..."
        rm -rf "${AGENT_INSTALLER_TEMP_DIR}"
    fi

    echo "Cleanup completed."
}

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

#==============================================================================
# Device Detection Functions
#==============================================================================

# Detect if the device uses flash storage (low-endurance flash memory)
detect_flash_device() {
    local target_dir="${UNISASE_ROOT}"

    # Get mount point device
    local mount_point=$(df "$target_dir" 2>/dev/null | awk 'NR==2 {print $1}')

    if [ -z "$mount_point" ]; then
        # If UNISASE_ROOT doesn't exist yet, check root directory
        mount_point=$(df / 2>/dev/null | awk 'NR==2 {print $1}')
    fi
    
    # Handle overlay filesystem
    if echo "$mount_point" | grep -q "overlay"; then
        # Find the actual underlying device
        local overlay_lower=$(mount | grep "on / type overlay" | sed -n 's/.*lowerdir=\([^,]*\).*/\1/p' | head -1)
        if [ -n "$overlay_lower" ]; then
            mount_point=$(df "$overlay_lower" 2>/dev/null | awk 'NR==2 {print $1}')
        fi
        
        # If still overlay, try /overlay directory
        if echo "$mount_point" | grep -q "overlay"; then
            mount_point=$(df /overlay 2>/dev/null | awk 'NR==2 {print $1}')
        fi
    fi
    
    # Check if device name contains "mmc" or "mmcblk"
    if echo "$mount_point" | grep -qE "mmc|mmcblk"; then
        echo "Detected flash storage device: $mount_point"
        return 0
    else
        echo "Detected regular disk storage: $mount_point"
        return 1
    fi
}

# Check if a directory is usable as tmpfs
# Requirements: mounted as tmpfs AND has 777 permissions
check_tmpfs_usable() {
    local dir=$1

    # Check if directory exists
    if [ ! -d "$dir" ]; then
        return 1
    fi

    # Check if mounted as tmpfs using df command
    local fs_type
    fs_type=$(df -T "$dir" 2>/dev/null | awk 'NR==2 {print $2}')
    if [ "$fs_type" != "tmpfs" ]; then
        return 1
    fi

    # Check permissions (should be 777 or 1777)
    local perms
    perms=$(stat -c "%a" "$dir" 2>/dev/null)
    if [ "$perms" != "777" ] && [ "$perms" != "1777" ]; then
        return 1
    fi

    return 0
}

# Select suitable tmpfs directory
# Priority: /run > /tmp
select_tmpfs_dir() {
    # Check /run first
    if check_tmpfs_usable "/run"; then
        echo "/run/unisase"
        return 0
    fi

    # Check /tmp second
    if check_tmpfs_usable "/tmp"; then
        echo "/tmp/unisase"
        return 0
    fi

    # No suitable tmpfs directory found
    echo ""
    return 1
}

# Check if available storage space is limited based on TMPFS_SPACE_THRESHOLD_MB.
# Returns 0 if available space is less than TMPFS_SPACE_THRESHOLD_MB, 1 otherwise.
#
# Parameters:
#   - target_dir: Directory to check (uses root directory if target doesn't exist)
check_storage_space() {
    local target_dir="${1:-${UNISASE_ROOT}}"

    # If target directory doesn't exist, check root directory
    if [ ! -d "$target_dir" ]; then
        target_dir="/"
    fi

    # Get available space in KB using df command
    # df output format: Filesystem 1K-blocks Used Available Use% Mounted
    # We need column 4 (Available)
    local available_kb=$(df "$target_dir" 2>/dev/null | awk 'NR==2 {print $4}')

    # Handle errors - if df command fails or returns invalid data
    if [ -z "$available_kb" ] || ! echo "$available_kb" | grep -qE '^[0-9]+$'; then
        echo "Warning: Failed to get available space for $target_dir, assuming sufficient space"
        return 1  # Assume sufficient space on error
    fi

    # Convert to MB for easier comparison
    local available_mb=$((available_kb / 1024))

    echo "Available space for $target_dir: ${available_mb}MB (threshold: ${TMPFS_SPACE_THRESHOLD_MB}MB)"

    # Return 0 if space is limited (< threshold), 1 if sufficient (>= threshold)
    if [ $available_mb -lt $TMPFS_SPACE_THRESHOLD_MB ]; then
        echo "Storage space is limited (< ${TMPFS_SPACE_THRESHOLD_MB}MB), should use tmpfs"
        return 0
    else
        echo "Storage space is sufficient (>= ${TMPFS_SPACE_THRESHOLD_MB}MB), can use regular directories"
        return 1
    fi
}

# Detect device type and select tmpfs directory
detect_device_and_tmpfs() {
    echo "Detecting device type and tmpfs availability..."

    # Primary detection: Check device type
    if detect_flash_device; then
        # Flash device detected, use tmpfs
        echo "Decision: Use tmpfs (flash device detected)"
        TMPFS_DIR=$(select_tmpfs_dir)
        if [ -n "$TMPFS_DIR" ]; then
            USE_TMPFS=true
            echo "Using tmpfs directory: $TMPFS_DIR"
        else
            echo "No suitable tmpfs directory found, using regular directories"
        fi
    else
        # Not a flash device, check storage space as fallback
        echo "Regular disk storage detected, checking available space..."

        if check_storage_space "${UNISASE_ROOT}"; then
            # Storage space is limited (below TMPFS_SPACE_THRESHOLD_MB), use tmpfs
            echo "Decision: Use tmpfs (limited storage space detected)"
            TMPFS_DIR=$(select_tmpfs_dir)
            if [ -n "$TMPFS_DIR" ]; then
                USE_TMPFS=true
                echo "Using tmpfs directory: $TMPFS_DIR"
            else
                echo "Warning: Limited storage space but no suitable tmpfs directory found"
                echo "Using regular directories (may cause storage issues)"
            fi
        else
            # Storage space is sufficient (>= TMPFS_SPACE_THRESHOLD_MB), use regular directories
            echo "Decision: Use regular directories (sufficient storage space)"
        fi
    fi
}

#==============================================================================
# Directory Creation Functions
#==============================================================================

# Ensure unisase directories exist
ensure_unisase_directories() {
    echo "Ensuring unisase directories exist..."

    if [ ! -d "$UNISASE_ROOT" ]; then
        echo "  Creating $UNISASE_ROOT..."
        mkdir -p "$UNISASE_ROOT"
        chmod 777 "$UNISASE_ROOT"
    fi

    if [ ! -d "$UNISASE_BIN_DIR" ]; then
        echo "  Creating $UNISASE_BIN_DIR..."
        mkdir -p "$UNISASE_BIN_DIR"
        chown nobody: "$UNISASE_BIN_DIR"
    fi

    if [ ! -d "$UNISASE_CONF_DIR" ]; then
        echo "  Creating $UNISASE_CONF_DIR..."
        mkdir -p "$UNISASE_CONF_DIR"
        chown nobody: "$UNISASE_CONF_DIR"
    fi

    if [ ! -d "$UNISASE_ARCHIVE_LOG_DIR" ]; then
        echo "  Creating $UNISASE_ARCHIVE_LOG_DIR..."
        mkdir -p "$UNISASE_ARCHIVE_LOG_DIR"
        chown nobody: "$UNISASE_ARCHIVE_LOG_DIR"
    fi

    # Prepare unisase runtime and log directories based on tmpfs usage
    if [ "$USE_TMPFS" = true ]; then
        echo "  Preparing unisase tmpfs directories in $TMPFS_DIR..."
        mkdir -p "$TMPFS_DIR/run"
        mkdir -p "$TMPFS_DIR/log"
        chown nobody: "$TMPFS_DIR/run"
        chown nobody: "$TMPFS_DIR/log"
    else
        echo "  Preparing unisase regular runtime and log directories..."
        mkdir -p "${UNISASE_RUNTIME_DIR}"
        mkdir -p "${UNISASE_LOG_DIR}"
        chown nobody: "${UNISASE_RUNTIME_DIR}"
        chown nobody: "${UNISASE_LOG_DIR}"
    fi

}

# Prepare unisase-agent directory structure
prepare_unisase_agent_directories() {
    echo "Preparing unisase-agent directory structure..."

    if [ ! -d "$AGENT_BIN_DIR" ]; then
        echo "  Creating $AGENT_BIN_DIR..."
        mkdir -p "$AGENT_BIN_DIR"
    fi

    if [ ! -d "$AGENT_CONF_DIR" ]; then
        echo "  Creating $AGENT_CONF_DIR..."
        mkdir -p "$AGENT_CONF_DIR"
    fi

    if [ ! -d "$AGENT_CONF_BACKUP_DIR" ]; then
        echo "  Creating $AGENT_CONF_BACKUP_DIR..."
        mkdir -p "$AGENT_CONF_BACKUP_DIR"
    fi

    if [ ! -d "$AGENT_CERTS_DIR" ]; then
        echo "  Creating $AGENT_CERTS_DIR..."
        mkdir -p "$AGENT_CERTS_DIR"
    fi

    if [ ! -d "$XRPC_CONF_DIR" ]; then
        echo "  Creating $XRPC_CONF_DIR..."
        mkdir -p "$XRPC_CONF_DIR"
    fi

    # Ensure archive log directory (actual storage location in /usr/panalog)
    # Note: AGENT_ARCHIVE_LOG_DIR will be a symlink to this location
    if [ ! -d "${DATAPATH}/unisase-agent" ]; then
        echo "  Creating ${DATAPATH}/unisase-agent..."
        mkdir -p "${DATAPATH}/unisase-agent"
    fi

    # Prepare runtime and log directories based on tmpfs usage
    if [ "$USE_TMPFS" = true ]; then
        echo "  Preparing tmpfs directories in $TMPFS_DIR..."
        mkdir -p "$TMPFS_DIR/run/unisase-agent"
        mkdir -p "$TMPFS_DIR/log/unisase-agent"
        mkdir -p "$TMPFS_DIR/run/unisase-xrpc"
        mkdir -p "$TMPFS_DIR/log/unisase-xrpc"
        mkdir -p "$TMPFS_DIR/run/unisase-ttyd"
        mkdir -p "$TMPFS_DIR/log/unisase-ttyd"
    else
        echo "  Preparing regular runtime and log directories..."
        mkdir -p "${AGENT_RUNTIME_DIR}"
        mkdir -p "${AGENT_DEFAULT_LOG_DIR}"
        mkdir -p "${XRPC_RUNTIME_DIR}"
        mkdir -p "${XRPC_LOG_DIR}"
        mkdir -p "${TTYD_RUNTIME_DIR}"
        mkdir -p "${TTYD_LOG_DIR}"
    fi

    # Ensure ramdisk and panabit app directories
    if [ ! -d "${RAM_ROOT_DIR}" ]; then
        echo "  Creating ${RAM_ROOT_DIR}..."
        mkdir -p "${RAM_ROOT_DIR}"
    fi

    if [ ! -d "${APP_ROOT_DIR}" ]; then
        echo "  Creating ${APP_ROOT_DIR}..."
        mkdir -p "${APP_ROOT_DIR}"
    fi

    # Ensure app log directory
    if [ ! -d "${APP_LOG_DIR}" ]; then
        echo "  Creating ${APP_LOG_DIR}..."
        mkdir -p "${APP_LOG_DIR}"
    fi
}

#==============================================================================
# Symlink Setup Functions
#==============================================================================

# Helper function to get symlink target without using readlink
# Uses ls -l to parse symlink target for better compatibility
# Parameters:
#   $1: symlink path
# Returns: symlink target path via stdout
get_symlink_target() {
    local symlink_path="$1"
    # Use ls -l to get symlink info, extract target after " -> "
    ls -l "$symlink_path" 2>/dev/null | sed 's/.* -> //'
}

# Helper function to create or update a symlink
# Parameters:
#   $1: symlink path
#   $2: target path
create_or_update_symlink() {
    local symlink_path="$1"
    local target_path="$2"

    # If symlink exists, check if target is correct
    if [ -L "$symlink_path" ]; then
        local current_target
        current_target=$(get_symlink_target "$symlink_path")
        if [ "$current_target" = "$target_path" ]; then
            echo "    Symlink already points to correct target, skipping"
            return 0
        else
            echo "    Updating symlink target from $current_target to $target_path"
            rm -f "$symlink_path"
        fi
    elif [ -d "$symlink_path" ]; then
        # If it's a directory, backup logs if it's the log directory
        if [ "$symlink_path" = "${UNISASE_LOG_DIR}" ]; then
            echo "    Directory exists, backing up logs before converting to symlink..."
            # Copy all log files to archive directory
            if [ -d "${AGENT_DEFAULT_LOG_DIR}" ] &&  [ -d "${AGENT_ARCHIVE_LOG_DIR}" ]; then
                for log_file in "${AGENT_DEFAULT_LOG_DIR}"/*; do
                    if [ -f "$log_file" ]; then
                        local log_name
                        log_name=$(basename "$log_file")
                        echo "      Backing up log: ${log_name}"
                        cp -p "$log_file" "${AGENT_ARCHIVE_LOG_DIR}/" || {
                            echo "      Warning: Failed to backup ${log_name}"
                        }
                    fi
                done
            fi
        fi
        echo "    Removing directory to create symlink"
        rm -rf "$symlink_path"
    fi

    # Create symlink using -sfn (force, no dereference)
    ln -sfn "$target_path" "$symlink_path"
    chown -h nobody: "$symlink_path"
}

# Setup symlinks
setup_symlinks() {
    echo "Setting up symlinks..."

    # Always create archive log symlink to /usr/panalog/unisase-agent
    echo "  Setting up archive log symlink..."
    local archive_parent_dir
    archive_parent_dir=$(dirname "${AGENT_ARCHIVE_LOG_DIR}")
    mkdir -p "$archive_parent_dir"
    create_or_update_symlink "${AGENT_ARCHIVE_LOG_DIR}" "${DATAPATH}/unisase-agent"

    # Create tmpfs symlinks if using tmpfs
    if [ "$USE_TMPFS" = true ]; then
        echo "  Setting up tmpfs symlinks..."

        # Runtime directory symlink
        echo "    Setting up runtime directory symlink..."
        local runtime_parent_dir
        runtime_parent_dir=$(dirname "${UNISASE_RUNTIME_DIR}")
        mkdir -p "$runtime_parent_dir"
        create_or_update_symlink "${UNISASE_RUNTIME_DIR}" "$TMPFS_DIR/run"

        # Log directory symlink
        echo "    Setting up log directory symlink..."
        local log_parent_dir
        log_parent_dir=$(dirname "${UNISASE_LOG_DIR}")
        mkdir -p "$log_parent_dir"
        create_or_update_symlink "${UNISASE_LOG_DIR}" "$TMPFS_DIR/log"
    fi
}

#==============================================================================
# Version and Migration Check Functions
#==============================================================================

# Get current installed unisase version
get_installed_unisase_version() {
    if [ -f "${UNISASE_VERSION_FILE}" ]; then
        cat "${UNISASE_VERSION_FILE}"
    else
        echo ""
    fi
}

# Check if migration is needed
# Migration is needed if version file doesn't exist or version is not current.
# The actual check for old installation existence is done in migrate script.
need_migration() {
    local installed_version=$(get_installed_unisase_version)

    # If version is already current, no migration needed
    if [ "$installed_version" = "${UNISASE_CURRENT_VERSION}" ]; then
        return 1
    fi

    # Version is not current, migration may be needed
    # The migrate script will check if old installation actually exists
    return 0
}

# Update unisase version file
update_unisase_version_file() {
    echo "Updating version file..."
    local version_dir=$(dirname "${UNISASE_VERSION_FILE}")
    mkdir -p "$version_dir"
    echo "${UNISASE_CURRENT_VERSION}" > "${UNISASE_VERSION_FILE}"
    echo "  Version file updated to ${UNISASE_CURRENT_VERSION}"
}

#==============================================================================
# Log Configuration Functions
#==============================================================================

# Calculate max-size based on available space
# Returns: max-size value in MB (32, 64, or 128)
#
# Parameters:
#   - log_dir: Log directory path
calculate_max_size() {
    local log_dir="$1"

    # Ensure directory exists for df command
    if [ ! -d "$log_dir" ]; then
        mkdir -p "$log_dir" 2>/dev/null || {
            echo "64"  # Default fallback
            return
        }
    fi

    # Get filesystem type and available space
    # df -T output: Filesystem Type 1K-blocks Used Available Use% Mounted
    # We need $2 (Type) and $5 (Available)
    local fs_info=$(df -T "$log_dir" 2>/dev/null | awk 'NR==2 {print $2, $5}')
    local fs_type=$(echo "$fs_info" | awk '{print $1}')
    local available_kb=$(echo "$fs_info" | awk '{print $2}')

    # Handle errors
    if [ -z "$available_kb" ] || ! echo "$available_kb" | grep -qE '^[0-9]+$'; then
        echo "64"  # Default fallback
        return
    fi

    local base_kb=$available_kb

    # If tmpfs, also check free memory and use the smaller value
    if [ "$fs_type" = "tmpfs" ]; then
        local free_mem_kb=$(free 2>/dev/null | awk '/^Mem:/ {print $4}')
        if [ -n "$free_mem_kb" ] && echo "$free_mem_kb" | grep -qE '^[0-9]+$'; then
            if [ "$free_mem_kb" -lt "$base_kb" ]; then
                base_kb=$free_mem_kb
            fi
        fi
    fi

    # Calculate 20% of base
    local log_max_kb=$((base_kb / 5))
    local log_max_mb=$((log_max_kb / 1024))

    # Determine max-size based on thresholds
    if [ $log_max_mb -ge 128 ]; then
        echo "128"
    elif [ $log_max_mb -ge 64 ]; then
        echo "64"
    else
        echo "32"
    fi
}

# Calculate max-backups based on available space and max-size
# Returns: max-backups value (1-50)
#
# Parameters:
#   - archive_dir: Archive directory path
#   - max_size: max-size value from calculate_max_size
calculate_max_backups() {
    local archive_dir="$1"
    local max_size="$2"

    # Ensure directory exists for df command
    if [ ! -d "$archive_dir" ]; then
        mkdir -p "$archive_dir" 2>/dev/null || {
            echo "10"  # Default fallback
            return
        }
    fi

    # Get available space for archive directory
    local available_kb=$(df "$archive_dir" 2>/dev/null | awk 'NR==2 {print $4}')

    # Handle errors
    if [ -z "$available_kb" ] || ! echo "$available_kb" | grep -qE '^[0-9]+$'; then
        echo "10"  # Default fallback
        return
    fi

    # Determine compressed size based on max-size
    local compressed_mb
    case $max_size in
        128) compressed_mb=4 ;;
        64)  compressed_mb=2 ;;
        32)  compressed_mb=1 ;;
        *)   compressed_mb=2 ;;  # Default
    esac

    # Calculate total log space (20% of available)
    local total_log_kb=$((available_kb / 5))
    local total_log_mb=$((total_log_kb / 1024))
    local available_for_backups=$((total_log_mb - max_size))

    # If space is insufficient, return minimum value
    if [ $available_for_backups -le 0 ]; then
        echo "1"
        return
    fi

    # Calculate max-backups: (total - max_size) / compressed_size
    local max_backups=$(( (total_log_mb - max_size) / compressed_mb ))

    # Apply constraints
    if [ $max_backups -gt 50 ]; then
        echo "50"
    elif [ $max_backups -ge 10 ]; then
        # Round down to nearest 10
        echo $(( (max_backups / 10) * 10 ))
    elif [ $max_backups -lt 1 ]; then
        echo "1"
    else
        echo "$max_backups"
    fi
}

# Update config.yaml with calculated values
# Uses sed to replace values while preserving YAML structure
#
# Parameters:
#   - config_file: Path to configuration file
#   - max_size: Calculated max-size value
#   - max_backups: Calculated max-backups value
update_log_config() {
    local config_file="$1"
    local max_size="$2"
    local max_backups="$3"

    if [ ! -f "$config_file" ]; then
        echo "  Warning: Config file not found: $config_file"
        return 1
    fi

    # Create backup
    cp "$config_file" "${config_file}.bak" || {
        echo "  Warning: Failed to create backup"
        return 1
    }

    # Update max-size (preserving indentation and comments)
    sed -i "s/^\([ ]*\)max-size:[ ]*[0-9]\+\(.*\)/\1max-size: ${max_size}\2/" "$config_file"

    # Update max-backups (preserving indentation and comments)
    sed -i "s/^\([ ]*\)max-backups:[ ]*[0-9]\+\(.*\)/\1max-backups: ${max_backups}\2/" "$config_file"

    # Verify changes were applied
    if grep -q "max-size: ${max_size}" "$config_file" && \
       grep -q "max-backups: ${max_backups}" "$config_file"; then
        rm "${config_file}.bak"
        echo "  Log configuration updated: max-size=${max_size}MB, max-backups=${max_backups}"
        return 0
    else
        # Restore backup on failure
        mv "${config_file}.bak" "$config_file"
        echo "  Warning: Failed to update log configuration, restored original"
        return 1
    fi
}

# Log calculation details for debugging
#
# Parameters:
#   - log_dir: Log directory path
#   - archive_dir: Archive directory path
#   - max_size: Calculated max-size value
#   - max_backups: Calculated max-backups value
log_calculation_details() {
    local log_dir="$1"
    local archive_dir="$2"
    local max_size="$3"
    local max_backups="$4"

    # Only log if APP_LOG_DIR exists
    if [ ! -d "${APP_LOG_DIR}" ]; then
        return
    fi

    {
        echo "=== Log Configuration Calculation Details ==="
        echo "Timestamp: $(date)"
        echo "Log directory: $log_dir"
        echo "Archive directory: $archive_dir"
        echo "Filesystem type: $(df -T "$log_dir" 2>/dev/null | awk 'NR==2 {print $2}')"
        echo "Available space: $(df -h "$log_dir" 2>/dev/null | awk 'NR==2 {print $4}')"
        if [ "$(df -T "$log_dir" 2>/dev/null | awk 'NR==2 {print $2}')" = "tmpfs" ]; then
            echo "Free memory: $(free -h 2>/dev/null | awk '/^Mem:/ {print $4}')"
        fi
        echo "Calculated max-size: ${max_size}MB"
        echo "Calculated max-backups: ${max_backups}"
        echo "============================================="
    } >> "${APPCTRL_LOG_FILE}" 2>/dev/null || true
}

# Configure log settings for default config template
# This updates the template BEFORE it's copied to the final location
configure_default_log_settings() {
    echo "Configuring default log settings based on system resources..."

    local default_config="${APP_ROOT_DIR}/config/config.yaml"

    # Ensure default config exists
    if [ ! -f "$default_config" ]; then
        echo "  Warning: Default config not found at $default_config"
        return 1
    fi

    # Calculate max-size based on log directory
    local max_size=$(calculate_max_size "${AGENT_DEFAULT_LOG_DIR}")
    echo "  Calculated max-size: ${max_size}MB"

    # Calculate max-backups based on archive directory
    local max_backups=$(calculate_max_backups "${AGENT_ARCHIVE_LOG_DIR}" "$max_size")
    echo "  Calculated max-backups: ${max_backups}"

    # Update default configuration template
    update_log_config "$default_config" "$max_size" "$max_backups"

    # Log calculation details for debugging
    log_calculation_details "${AGENT_DEFAULT_LOG_DIR}" "${AGENT_ARCHIVE_LOG_DIR}" \
                            "$max_size" "$max_backups"
}

#==============================================================================
# File Copy Functions
#==============================================================================

# Copy binary files
copy_binaries() {
    echo "Copying binary files..."

    # app.pak location (in /tmp, moved by preinstall to save RAMDISK space)
    APP_PAK_PATH="${AGENT_INSTALLER_PAK_DIR}/app.pak"

    # Check if app.pak exists
    if [ ! -f "${APP_PAK_PATH}" ]; then
        echo "  Error: app.pak not found at ${APP_PAK_PATH}"
        return 1
    fi

    echo "  Found app.pak at ${APP_PAK_PATH}"
    echo "  Detected compressed binary package (app.pak)"

    # Extract to temporary directory under /usr/unisase/run
    # At this point, setup_symlinks() has already run, so /usr/unisase/run
    # is symlinked to tmpfs, meaning extraction happens in tmpfs
    echo "  Extracting app.pak to ${AGENT_INSTALLER_TEMP_DIR}..."

    # Create extraction directory
    mkdir -p "${AGENT_INSTALLER_TEMP_DIR}"

    if tar -xJf "${APP_PAK_PATH}" -C "${AGENT_INSTALLER_TEMP_DIR}"; then
        echo "  Successfully extracted binary files to tmpfs"

        # Copy binaries from extraction directory
        BIN_SOURCE_DIR="${AGENT_INSTALLER_TEMP_DIR}/bin"

        # Copy unisase-agent using atomic replacement
        if [ -f "${BIN_SOURCE_DIR}/unisase-agent" ]; then
            echo "  Copying unisase-agent to ${AGENT_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-agent" "${AGENT_BIN_DIR}/unisase-agent.new"
            chmod +x "${AGENT_BIN_DIR}/unisase-agent.new"
            mv -f "${AGENT_BIN_DIR}/unisase-agent.new" "${AGENT_BIN_DIR}/unisase-agent"
        fi

        # Copy unisase-agent-updater script using atomic replacement
        if [ -f "${BIN_SOURCE_DIR}/unisase-agent-updater" ]; then
            echo "  Copying unisase-agent-updater to ${AGENT_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-agent-updater" "${AGENT_BIN_DIR}/unisase-agent-updater.new"
            chmod +x "${AGENT_BIN_DIR}/unisase-agent-updater.new"
            mv -f "${AGENT_BIN_DIR}/unisase-agent-updater.new" "${AGENT_BIN_DIR}/unisase-agent-updater"
        fi

        # Copy remote access component binaries
        echo "  Copying remote access component binaries..."

        # Copy unisase-xrpc using atomic replacement (supports hot update)
        if [ -f "${BIN_SOURCE_DIR}/unisase-xrpc" ]; then
            echo "    Copying unisase-xrpc to ${UNISASE_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-xrpc" "${UNISASE_BIN_DIR}/unisase-xrpc.new"
            chmod +x "${UNISASE_BIN_DIR}/unisase-xrpc.new"
            mv -f "${UNISASE_BIN_DIR}/unisase-xrpc.new" "${UNISASE_BIN_DIR}/unisase-xrpc"
        fi

        # Copy unisase-ttyd using atomic replacement (supports hot update)
        if [ -f "${BIN_SOURCE_DIR}/unisase-ttyd" ]; then
            echo "    Copying unisase-ttyd to ${UNISASE_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-ttyd" "${UNISASE_BIN_DIR}/unisase-ttyd.new"
            chmod +x "${UNISASE_BIN_DIR}/unisase-ttyd.new"
            mv -f "${UNISASE_BIN_DIR}/unisase-ttyd.new" "${UNISASE_BIN_DIR}/unisase-ttyd"
        fi

        # Copy control scripts
        echo "  Copying remote access control scripts..."

        # Copy unisase-xrpc-ctl using atomic replacement
        if [ -f "${BIN_SOURCE_DIR}/unisase-xrpc-ctl" ]; then
            echo "    Copying unisase-xrpc-ctl to ${UNISASE_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-xrpc-ctl" "${UNISASE_BIN_DIR}/unisase-xrpc-ctl.new"
            chmod +x "${UNISASE_BIN_DIR}/unisase-xrpc-ctl.new"
            mv -f "${UNISASE_BIN_DIR}/unisase-xrpc-ctl.new" "${UNISASE_BIN_DIR}/unisase-xrpc-ctl"
        fi

        # Copy unisase-ttyd-ctl using atomic replacement
        if [ -f "${BIN_SOURCE_DIR}/unisase-ttyd-ctl" ]; then
            echo "    Copying unisase-ttyd-ctl to ${UNISASE_BIN_DIR}..."
            cp "${BIN_SOURCE_DIR}/unisase-ttyd-ctl" "${UNISASE_BIN_DIR}/unisase-ttyd-ctl.new"
            chmod +x "${UNISASE_BIN_DIR}/unisase-ttyd-ctl.new"
            mv -f "${UNISASE_BIN_DIR}/unisase-ttyd-ctl.new" "${UNISASE_BIN_DIR}/unisase-ttyd-ctl"
        fi
    else
        echo "  Error: Failed to extract app.pak"
        return 1
    fi
}

# Copy configuration files
copy_config() {
    echo "Copying configuration files..."

    # Only copy default config if no config exists
    if [ ! -f "${AGENT_CONF_FILE}" ]; then
        echo "  Creating default config file..."
        cp "${APP_ROOT_DIR}/config/config.yaml" "${AGENT_CONF_DIR}/"
    else
        echo "  Config file already exists, skipping..."
    fi

    # Set config file permissions
    if [ -f "${AGENT_CONF_FILE}" ]; then
        chmod 644 "${AGENT_CONF_FILE}"
    fi
}

# Copy certificate files
copy_certs() {
    echo "Copying certificate files..."

    if [ -d "${APP_ROOT_DIR}/certs" ]; then
        cp -a "${APP_ROOT_DIR}/certs/." "${AGENT_CERTS_DIR}/"
        find "${AGENT_CERTS_DIR}" -type d -exec chmod 755 {} \;
        find "${AGENT_CERTS_DIR}" -type f -exec chmod 644 {} \;
    fi
}

# Copy web files
copy_web_files() {
    echo "Copying web files..."
    
    if [ ! -d "${RAM_ROOT_DIR}/web" ]; then
        return
    fi
    
    # Set web interface permissions
    find "${RAM_ROOT_DIR}/web" -type d -exec chmod 755 {} \;
    find "${RAM_ROOT_DIR}/web" -type f -name "*.html" -exec chmod 644 {} \;
    find "${RAM_ROOT_DIR}/web" -type f -name "*.css" -exec chmod 644 {} \;
    find "${RAM_ROOT_DIR}/web" -type f -name "*.js" -exec chmod 644 {} \;
    find "${RAM_ROOT_DIR}/web/cgi" -type f -exec chmod +x {} \; 2>/dev/null || true

    # Copy to ramdisk web root
    echo "  Copying web files to ramdisk..."
    mkdir -p "${RAM_WEB_ROOT}/html/App/${APP_NAME}"
    mkdir -p "${RAM_WEB_ROOT}/cgi-bin/App/${APP_NAME}"

    if [ -d "${RAM_ROOT_DIR}/web/html" ]; then
        cp -a "${RAM_ROOT_DIR}/web/html"/* "${RAM_WEB_ROOT}/html/App/${APP_NAME}/"
    fi

    if [ -d "${RAM_ROOT_DIR}/web/cgi" ]; then
        cp -a "${RAM_ROOT_DIR}/web/cgi"/* "${RAM_WEB_ROOT}/cgi-bin/App/${APP_NAME}/"
        chmod +x "${RAM_WEB_ROOT}/cgi-bin/App/${APP_NAME}"/*
    fi

    # Copy to panabit web directory
    echo "  Copying web files to panabit web directory..."
    mkdir -p "${PA_WEB_ROOT}/html/App/${APP_NAME}"
    mkdir -p "${PA_WEB_ROOT}/cgi-bin/App/${APP_NAME}"

    if [ -d "${RAM_ROOT_DIR}/web/html" ]; then
        cp -a "${RAM_ROOT_DIR}/web/html"/* "${PA_WEB_ROOT}/html/App/${APP_NAME}/"
    fi

    if [ -d "${RAM_ROOT_DIR}/web/cgi" ]; then
        cp -a "${RAM_ROOT_DIR}/web/cgi"/* "${PA_WEB_ROOT}/cgi-bin/App/${APP_NAME}/"
        chmod +x "${PA_WEB_ROOT}/cgi-bin/App/${APP_NAME}"/*
    fi
}

#==============================================================================
# Permission Setup Functions
#==============================================================================

# Set permissions for all directories and files
set_permissions() {
    echo "Setting permissions..."

    # Set monitor script permissions
    if [ -f "${MONITOR_SCRIPT}" ]; then
        chmod +x "${MONITOR_SCRIPT}"
    fi

    # Change owner and group to nobody
    if [ "$(whoami)" != "nobody" ]; then
        echo "  Changing owner to nobody..."

        # Change owner for version file
        chown nobody: "${UNISASE_VERSION_FILE}" 2>/dev/null || true

        # Change owner for other directories
        chown -R nobody: "${AGENT_BIN_DIR}" 2>/dev/null || true
        chown -R nobody: "${AGENT_CONF_DIR}" 2>/dev/null || true
        chown -R nobody: "${AGENT_CERTS_DIR}" 2>/dev/null || true
        chown -R nobody: "${AGENT_RUNTIME_DIR}" 2>/dev/null || true
        chown -R nobody: "${AGENT_DEFAULT_LOG_DIR}" 2>/dev/null || true

        chown -R nobody: "${XRPC_CONF_DIR}" 2>/dev/null || true
        chown -R nobody: "${XRPC_RUNTIME_DIR}" 2>/dev/null || true
        chown -R nobody: "${XRPC_LOG_DIR}" 2>/dev/null || true
        chown -R nobody: "${TTYD_RUNTIME_DIR}" 2>/dev/null || true
        chown -R nobody: "${TTYD_LOG_DIR}" 2>/dev/null || true

        chown -R nobody: "${DATAPATH}/unisase-agent" 2>/dev/null || true
        chown -R nobody: "${APP_LOG_DIR}" 2>/dev/null || true

        # Self-update directories (RAM_ROOT_DIR / APP_ROOT_DIR / the four web
        # dirs) are owned by ensure_agent_access_permissions (called below),
        # which is the single source of truth and also re-runs every boot.

        # Change owner for tmpfs directories if used
        if [ "$USE_TMPFS" = true ] && [ -n "$TMPFS_DIR" ]; then
            chown -R nobody: "$TMPFS_DIR" 2>/dev/null || true
        fi

        # Single source of truth for external-path ownership + access bits
        # (self-update dirs ownership; umask-0027 traverse/read/exec grants).
        # Grouped with the privileged chowns above; the function's own
        # EUID-based root check backstops environments lacking 'whoami'.
        ensure_agent_access_permissions
    fi
}

#==============================================================================
# Cleanup Functions
#==============================================================================

# Remove unnecessary files
cleanup() {
    echo "Removing unnecessary files..."

    sync

    # Remove web files from ramdisk
    rm -rf "${RAM_ROOT_DIR}/web"

    # Remove certificate files from ramdisk
    rm -rf "${RAM_ROOT_DIR}/certs"

    # Remove temporary files from ramdisk
    rm -rf "${RAM_ROOT_DIR}/tmp"

    # Remove web files from panabit app directory
    rm -rf "${APP_ROOT_DIR}/web"

    # Remove certificate files from panabit app directory
    rm -rf "${APP_ROOT_DIR}/certs"

    # Remove app.pak directory (in /tmp)
    rm -rf "${AGENT_INSTALLER_PAK_DIR}"

    # Remove extraction directory (in /usr/unisase/run, which is symlinked to tmpfs)
    rm -rf "${AGENT_INSTALLER_TEMP_DIR}"
}

#==============================================================================
# Main Installation Process
#==============================================================================

main() {
    # Step 1: Detect device type and tmpfs
    detect_device_and_tmpfs

    # Step 2: Ensure unisase directories exist
    ensure_unisase_directories

    # Step 3: Prepare unisase-agent directories
    prepare_unisase_agent_directories

    # Step 4: Setup symlinks
    setup_symlinks

    # Step 5: Check if migration is needed
    if need_migration; then
        # Source migration functions
        if [ -f "${SCRIPT_DIR}/migrate" ]; then
            . "${SCRIPT_DIR}/migrate"

            # Perform migration
            if ! perform_migration; then
                echo "Warning: Migration completed with some errors."
            fi
        else
            echo "Warning: Migration script not found at ${SCRIPT_DIR}/migrate, skipping migration."
        fi
    else
        echo "No migration needed."
    fi

    # Step 6: Copy new files
    if ! copy_binaries; then
        echo "Error: Failed to copy binary files, installation aborted."
        return 1
    fi

    # Step 6.1: Configure default log settings BEFORE copying config
    # This ensures the default template is properly configured for fresh installations
    configure_default_log_settings

    # Step 6.2: Copy config (only if it doesn't exist - fresh installation)
    copy_config

    copy_certs
    copy_web_files

    # Step 7: Update version file
    update_unisase_version_file

    # Step 8: Set permissions
    set_permissions

    # Step 9: Cleanup
    cleanup

    echo "Post-installation configuration completed successfully."
    return 0
}

#==============================================================================
# Main Execution
#==============================================================================

main "$@"
exit $?
