#!/bin/bash

################################################################################
# System Update and Maintenance Script
################################################################################
#
# Author: Peter Wood <peter@peterwood.dev>
# Description: Comprehensive system update script with advanced error detection,
#              automatic remediation of common package manager issues, and
#              intelligent service management.
#
# Features:
#   - Cross-platform package manager detection (apt/nala/dnf)
#   - Automatic detection and remediation of corrupt package lists
#   - APT database lock handling and recovery
#   - Broken dependency detection and repair
#   - Service-aware updating with Plex management
#   - Oh My Zsh upgrade integration
#   - Comprehensive error handling and logging
#   - Detailed debugging output for troubleshooting
#
# Common Issues Remediated:
#   - Corrupt package cache and lists
#   - APT database locks and conflicts
#   - Interrupted package installations
#   - Broken or missing dependencies
#   - GPG key validation errors
#   - Network connectivity issues during updates
#
# Usage:
#   ./update.sh                    # Standard system update
#   ./update.sh --debug           # Enhanced debugging output
#   ./update.sh --force-repair    # Force package manager repair
#   ./update.sh --skip-services   # Skip all service management
#   ./update.sh --skip-plex       # Skip only Plex-related operations
#
# Dependencies:
#   - sudo privileges
#   - Package manager (apt/nala/dnf)
#   - systemctl (for service management)
#   - Oh My Zsh (optional)
#
# Exit Codes:
#   0 - Success
#   1 - General error
#   2 - Package manager issues
#   3 - Service management failure
#   4 - Permission denied
#
################################################################################

set -e

# Color codes for output formatting
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly RED='\033[0;31m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m' # No Color

# Configuration
if [[ -w "/var/log" ]]; then
    LOG_FILE="/var/log/system-update.log"
else
    LOG_FILE="$HOME/.local/share/system-update.log"
    mkdir -p "$(dirname "$LOG_FILE")"
fi
readonly LOG_FILE

# Global variables
ERRORS_DETECTED=0
REMEDIATION_ATTEMPTED=0
PKG_MANAGER=""
OS_NAME=""
OS_VERSION=""

# Command line flags
DEBUG_MODE=false
FORCE_REPAIR=false
SKIP_SERVICES=false
SKIP_PLEX=false
TEST_MODE=false

################################################################################
# Utility Functions
################################################################################

# Logging function with timestamps
log_message() {
    local level="$1"
    local message="$2"
    local timestamp
    local color=""

    timestamp=$(date '+%Y-%m-%d %H:%M:%S')

    case "$level" in
        "INFO")  color="$GREEN" ;;
        "WARN")  color="$YELLOW" ;;
        "ERROR") color="$RED" ;;
        "DEBUG") color="$CYAN" ;;
        *)       color="$NC" ;;
    esac

    echo -e "${color}[$timestamp] [$level] $message${NC}"

    # Log to file if possible
    if [[ -w "$(dirname "$LOG_FILE")" ]] 2>/dev/null; then
        echo "[$timestamp] [$level] $message" >> "$LOG_FILE" 2>/dev/null || true
    fi
}

# Debug logging (only when debug mode enabled)
debug_log() {
    if [[ "$DEBUG_MODE" == true ]]; then
        log_message "DEBUG" "$1"
    fi
}

# Error counter
increment_error() {
    ((ERRORS_DETECTED++))
    log_message "ERROR" "$1"
}

# Remediation counter
increment_remediation() {
    ((REMEDIATION_ATTEMPTED++))
    log_message "INFO" "REMEDIATION: $1"
}

# Check if running with sufficient privileges
check_privileges() {
    if [[ "$TEST_MODE" == true ]]; then
        debug_log "Test mode enabled - skipping privilege check"
        return 0
    fi

    if [[ $EUID -eq 0 ]]; then
        log_message "WARN" "Running as root - this is not recommended"
        return 0
    fi

    # Check if sudo is available, but don't require it upfront
    if sudo -n true 2>/dev/null; then
        debug_log "Sudo privileges confirmed and cached"
    else
        log_message "INFO" "Sudo credentials not cached - you may be prompted for password during updates"
        log_message "INFO" "To avoid prompts, run: sudo -v before running this script"
    fi

    return 0
}

# Cleanup function for script exit
cleanup() {
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        log_message "ERROR" "Script exited with error code: $exit_code"
    fi

    log_message "INFO" "Script completed - Errors: $ERRORS_DETECTED, Remediations: $REMEDIATION_ATTEMPTED"
    exit $exit_code
}

trap cleanup EXIT ERR

################################################################################
# Argument Parsing
################################################################################

# Parse command line arguments
parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --debug)
                DEBUG_MODE=true
                debug_log "Debug mode enabled"
                shift
                ;;
            --force-repair)
                FORCE_REPAIR=true
                shift
                ;;
            --skip-services)
                SKIP_SERVICES=true
                shift
                ;;
            --skip-plex)
                SKIP_PLEX=true
                shift
                ;;
            --test-mode)
                TEST_MODE=true
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            *)
                log_message "WARN" "Unknown argument: $1"
                shift
                ;;
        esac
    done
}

# Show help message
show_help() {
    cat << EOF
System Update and Maintenance Script

Usage: $0 [OPTIONS]

OPTIONS:
    --debug           Enable enhanced debugging output
    --force-repair    Force package manager repair before updates
    --skip-services   Skip all service management (includes Plex)
    --skip-plex       Skip only Plex-related operations
    --test-mode       Test flag parsing without running privileged commands
    -h, --help        Show this help message

Examples:
    $0                    # Standard system update
    $0 --debug           # Enhanced debugging output
    $0 --skip-plex       # Skip Plex service management
    $0 --force-repair    # Force package manager repair
    $0 --skip-services   # Skip all service management
    $0 --test-mode       # Test flag parsing without privileged commands

Exit Codes:
    0 - Success
    1 - General error
    2 - Package manager issues
    3 - Service management failure
    4 - Permission denied
EOF
}

################################################################################
# System Detection and Package Manager Setup
################################################################################

# Detect operating system
detect_os() {
    log_message "INFO" "Detecting operating system..."

    if [[ -f /etc/os-release ]]; then
        # shellcheck disable=SC1091
        . /etc/os-release
        OS_NAME="$ID"
        OS_VERSION="$VERSION_ID"
        log_message "INFO" "Detected OS: $OS_NAME $OS_VERSION"
    else
        log_message "WARN" "Unable to detect OS from /etc/os-release, assuming Ubuntu"
        OS_NAME="ubuntu"
        OS_VERSION="unknown"
    fi

    debug_log "OS detection completed: $OS_NAME ($OS_VERSION)"
}

# Determine the best package manager to use
determine_pkg_manager() {
    log_message "INFO" "Determining package manager..."

    if command -v nala &> /dev/null; then
        PKG_MANAGER="nala"
        log_message "INFO" "Using nala (enhanced apt frontend)"
    elif [[ "$OS_NAME" == "fedora" ]] || [[ "$OS_NAME" == "rhel" ]] || [[ "$OS_NAME" == "centos" ]]; then
        PKG_MANAGER="dnf"
        log_message "INFO" "Using dnf (Red Hat family)"
    elif command -v apt &> /dev/null; then
        PKG_MANAGER="apt"
        log_message "INFO" "Using apt (Debian family)"
    else
        increment_error "No supported package manager found"
        exit 2
    fi

    debug_log "Package manager determination completed: $PKG_MANAGER"
}

################################################################################
# Error Detection and Remediation Functions
################################################################################

# Check for and fix APT database locks
fix_apt_locks() {
    log_message "INFO" "Checking for APT database locks..."

    local lock_files=(
        "/var/lib/dpkg/lock"
        "/var/lib/dpkg/lock-frontend"
        "/var/cache/apt/archives/lock"
        "/var/lib/apt/lists/lock"
    )

    local locks_found=false

    for lock_file in "${lock_files[@]}"; do
        if [[ -f "$lock_file" ]]; then
            debug_log "Found lock file: $lock_file"
            locks_found=true
        fi
    done

    if $locks_found; then
        increment_remediation "Removing APT database locks"

        # Kill any running apt processes
        sudo pkill -f apt &>/dev/null || true
        sudo pkill -f dpkg &>/dev/null || true

        # Remove lock files
        for lock_file in "${lock_files[@]}"; do
            if [[ -f "$lock_file" ]]; then
                sudo rm -f "$lock_file"
                debug_log "Removed lock file: $lock_file"
            fi
        done

        # Reconfigure interrupted packages
        sudo dpkg --configure -a
        log_message "INFO" "APT locks cleared and packages reconfigured"
    else
        debug_log "No APT locks detected"
    fi
}

# Check for and fix corrupt package lists
fix_corrupt_lists() {
    log_message "INFO" "Checking for corrupt package lists..."

    local lists_dir="/var/lib/apt/lists"
    local corrupt_found=false

    # Check if lists directory exists and is accessible
    if [[ ! -d "$lists_dir" ]]; then
        increment_error "APT lists directory not found: $lists_dir"
        return 1
    fi

    # Look for partial or corrupt files
    if find "$lists_dir" -name "*_Packages" -size 0 2>/dev/null | grep -q .; then
        corrupt_found=true
        debug_log "Found zero-size package files"
    fi

    if find "$lists_dir" -name "partial" -type d 2>/dev/null | grep -q .; then
        if [[ -n "$(ls -A "$lists_dir/partial" 2>/dev/null)" ]]; then
            corrupt_found=true
            debug_log "Found files in partial directory"
        fi
    fi

    if $corrupt_found; then
        increment_remediation "Cleaning corrupt package lists"

        # Remove partial downloads and corrupt files
        sudo rm -rf "$lists_dir/partial/"*
        sudo find "$lists_dir" -name "*_Packages" -size 0 -delete

        log_message "INFO" "Corrupt package lists cleaned"
    else
        debug_log "No corrupt package lists detected"
    fi
}

# Check network connectivity
check_network_connectivity() {
    log_message "INFO" "Checking network connectivity..."

    local test_hosts=("8.8.8.8" "1.1.1.1")
    local connectivity=false

    for host in "${test_hosts[@]}"; do
        if ping -c 1 -W 3 "$host" &>/dev/null; then
            connectivity=true
            debug_log "Network connectivity confirmed via $host"
            break
        fi
    done

    if ! $connectivity; then
        increment_error "No network connectivity detected"
        return 1
    fi

    debug_log "Network connectivity check passed"
    return 0
}

# Repair broken dependencies
fix_broken_dependencies() {
    log_message "INFO" "Checking for broken dependencies..."

    case "$PKG_MANAGER" in
        apt|nala)
            # Check for broken packages
            local broken_output
            broken_output=$(sudo apt-get check 2>&1 || true)

            if echo "$broken_output" | grep -q "broken"; then
                increment_remediation "Fixing broken dependencies"
                sudo apt-get install -f -y
                sudo apt-get autoremove -y
                log_message "INFO" "Broken dependencies repair attempted"
            else
                debug_log "No broken dependencies detected"
            fi
            ;;
        dnf)
            # DNF has built-in dependency resolution
            debug_log "DNF handles dependencies automatically"
            ;;
    esac
}

# Comprehensive package manager health check
check_package_manager_health() {
    log_message "INFO" "Performing package manager health check..."

    case "$PKG_MANAGER" in
        apt|nala)
            check_network_connectivity || return 1
            fix_apt_locks
            fix_corrupt_lists
            fix_broken_dependencies
            ;;
        dnf)
            check_network_connectivity || return 1
            # DNF is generally more robust, fewer common issues
            debug_log "DNF health check completed"
            ;;
    esac

    log_message "INFO" "Package manager health check completed"
}

################################################################################
# Service Management Functions
################################################################################

# Manage Plex service during updates
manage_plex_service() {
    local action="$1"
    local plex_script="/home/acedanger/shell/plex/plex.sh"

    if ! systemctl list-unit-files plexmediaserver.service &>/dev/null; then
        debug_log "Plex Media Server service not found on this system"
        return 0
    fi

    case "$action" in
        "stop")
            if systemctl is-active --quiet plexmediaserver.service 2>/dev/null; then
                log_message "INFO" "Stopping Plex Media Server for system update"
                if [[ -x "$plex_script" ]]; then
                    PLEX_SIMPLE_OUTPUT=1 "$plex_script" stop
                else
                    sudo systemctl stop plexmediaserver.service
                fi
                debug_log "Plex service stopped successfully"
            else
                debug_log "Plex service is not running"
            fi
            ;;
        "start")
            if systemctl is-enabled --quiet plexmediaserver.service 2>/dev/null; then
                log_message "INFO" "Starting Plex Media Server after system update"
                if [[ -x "$plex_script" ]]; then
                    PLEX_SIMPLE_OUTPUT=1 "$plex_script" start
                else
                    sudo systemctl start plexmediaserver.service
                fi
                debug_log "Plex service started successfully"
            else
                debug_log "Plex service is not enabled"
            fi
            ;;
    esac
}

################################################################################
# Update Functions
################################################################################

# Upgrade Oh My Zsh
upgrade_oh_my_zsh() {
    local omz_upgrade_script="$HOME/.oh-my-zsh/tools/upgrade.sh"

    log_message "INFO" "Checking for Oh My Zsh upgrade..."

    if [[ -x "$omz_upgrade_script" ]]; then
        log_message "INFO" "Upgrading Oh My Zsh..."
        if "$omz_upgrade_script"; then
            log_message "INFO" "Oh My Zsh upgrade completed successfully"
        else
            increment_error "Oh My Zsh upgrade failed"
        fi
    else
        debug_log "Oh My Zsh not found or upgrade script not executable"
    fi
}

# Perform system package updates
perform_system_update() {
    log_message "INFO" "Starting system package update with $PKG_MANAGER..."

    case "$PKG_MANAGER" in
        nala)
            log_message "INFO" "Updating package lists with nala..."
            if ! sudo nala update; then
                increment_error "Failed to update package lists with nala"
                return 1
            fi

            log_message "INFO" "Upgrading packages with nala..."
            if ! sudo nala upgrade -y; then
                increment_error "Failed to upgrade packages with nala"
                return 1
            fi

            log_message "INFO" "Cleaning up unused packages with nala..."
            sudo nala autoremove -y
            ;;
        dnf)
            log_message "INFO" "Checking for updates with dnf..."
            sudo dnf check-update -y || true  # Exit code 100 means updates available

            log_message "INFO" "Upgrading packages with dnf..."
            if ! sudo dnf upgrade -y; then
                increment_error "Failed to upgrade packages with dnf"
                return 1
            fi

            log_message "INFO" "Cleaning up unused packages with dnf..."
            sudo dnf autoremove -y
            ;;
        apt)
            log_message "INFO" "Updating package lists with apt..."
            if ! sudo apt update; then
                increment_error "Failed to update package lists with apt"
                return 1
            fi

            log_message "INFO" "Upgrading packages with apt..."
            if ! sudo apt upgrade -y; then
                increment_error "Failed to upgrade packages with apt"
                return 1
            fi

            log_message "INFO" "Cleaning up unused packages with apt..."
            sudo apt autoremove -y && sudo apt autoclean
            ;;
    esac

    # Universal packages
    if command -v flatpak &> /dev/null; then
        log_message "INFO" "Updating Flatpak packages..."
        flatpak update -y

        log_message "INFO" "Cleaning up unused Flatpak runtimes..."
        flatpak uninstall --unused -y
    fi

    if command -v snap &> /dev/null; then
        log_message "INFO" "Updating Snap packages..."
        sudo snap refresh
    fi

    log_message "INFO" "System package update completed successfully"
}

update_signal() {
    # check if hostname is `mini`
    if [[ "$(hostname)" != "mini" ]]; then
        debug_log "Signal update is only available on host 'mini'"
        return 0
    fi

    # check if distrobox is installed
    if ! command -v distrobox-upgrade &> /dev/null; then
        debug_log "distrobox is not installed"
        return 0
    fi

    # Capture failure to prevent script exit due to set -e
    # Known issue: distrobox-upgrade may throw a stat error at the end despite success
    if ! distrobox-upgrade signal; then
        log_message "WARN" "Signal update reported an error (likely benign 'stat' issue). Continuing..."
    fi
}

################################################################################
# Main Execution
################################################################################

main() {
    # Parse command line arguments first
    parse_arguments "$@"

    log_message "INFO" "Starting system update process..."

    # Check privileges
    check_privileges

    # Detect system and package manager
    detect_os
    determine_pkg_manager

    # Pre-update health checks and repairs
    if [[ "$FORCE_REPAIR" == true ]] || ! check_package_manager_health; then
        log_message "WARN" "Package manager health issues detected, attempting repairs..."
        check_package_manager_health
    fi

    # Stop services that might interfere with updates
    if [[ "$SKIP_SERVICES" != true ]]; then
        if [[ "$SKIP_PLEX" != true ]]; then
            manage_plex_service "stop"
        else
            debug_log "Skipping Plex service stop due to --skip-plex flag"
        fi
    else
        debug_log "Skipping all service management due to --skip-services flag"
    fi

    # Perform updates
    upgrade_oh_my_zsh
    perform_system_update

    # signal is made available using distrobox and is only available on `mini`
    update_signal

    # Restart services
    if [[ "$SKIP_SERVICES" != true ]]; then
        if [[ "$SKIP_PLEX" != true ]]; then
            manage_plex_service "start"
        else
            debug_log "Skipping Plex service start due to --skip-plex flag"
        fi
    else
        debug_log "Skipping all service management due to --skip-services flag"
    fi

    # Check for reboot requirement
    if [[ -f /var/run/reboot-required ]]; then
        log_message "WARN" "A system reboot is required to complete the update."
    fi

    # Final status
    if [[ $ERRORS_DETECTED -eq 0 ]]; then
        log_message "INFO" "System update completed successfully!"
    else
        log_message "WARN" "System update completed with $ERRORS_DETECTED errors"
        exit 1
    fi
}

# Execute main function
main "$@"
