#!/usr/bin/env bash
# =============================================================================
# cloudmon-install.sh
# Cloudmon — Production Installer / Uninstaller for Ubuntu 24.04
# Based on official Cloudmon Installation Guide v1.4.0
#
# This script has two completely independent top-level operations:
#   --install     Never removes, purges, or modifies an existing installation.
#                 Only checks whether a component is already present/healthy
#                 and skips it if so.
#   --uninstall   The ONLY mode that stops services, purges packages, deletes
#                 directories/config/databases, and removes repositories.
# =============================================================================

set -Eeuo pipefail

# =============================================================================
# CONSTANTS
# =============================================================================

readonly SCRIPT_VERSION="2.0.0"
readonly SUPPORTED_VERSIONS=("24.04")

readonly LOG_DIR_DEFAULT="/var/log/cloudmon-installer"
readonly NODEJS_MAJOR_REQUIRED=24
readonly NGINX_MINOR_REQUIRED=25
readonly MONGODB_VERSION="8.0"
readonly NGINX_SIGNING_KEY_URL="https://nginx.org/keys/nginx_signing.key"

readonly LOCAL_MONGO_DB_USER="cloudmon"
readonly LOCAL_MONGO_DB_PASS="ZeroDowntime"
readonly CLOUDMON_DB_NAME="cloudmon"
readonly CLOUDMON_SERVER_DIR="/usr/local/cloudmon/server"
readonly CLOUDMON_COLLECTOR_DIR="/usr/local/cloudmon/collector"
readonly CLOUDMON_PROBE_DIR="/usr/local/cloudmon/probe"
readonly CLOUDMON_NGINX_CONF="/usr/local/cloudmon/server/nginx/conf.d/cloudmon.conf"

readonly TMP_DIR="$(mktemp -d /tmp/cloudmon-install.XXXXXX)"

# =============================================================================
# COLORS
# =============================================================================

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

# =============================================================================
# GLOBAL STATE
# =============================================================================

LOG_DIR="${LOG_DIR_DEFAULT}"
LOG_FILE=""

NON_INTERACTIVE=false
USAGE_ERROR=false   # set true when exiting due to invalid/incomplete CLI usage,
                     # so cleanup() shows a clean usage prompt instead of a
                     # generic "script exited with code" error line.

MODE=""   # "install" or "uninstall"

CONTROLLER_HOST=""
API_KEY=""

DB_HOST=""
DB_USER=""
DB_PASS=""
PACKAGE_URL=""

DO_CONTROLLER=false
DO_COLLECTOR=false
DO_MONGODB=false
DO_REDIS=false
DO_MONGOSH=false

URL_CONTROLLER=""
URL_COLLECTOR=""
URL_PROBE=""

UBUNTU_VERSION=""
UBUNTU_CODENAME=""

declare -a SUMMARY_COMPONENT=()
declare -a SUMMARY_STATUS=()
declare -a SUMMARY_DETAIL=()

# =============================================================================
# CLEANUP / TRAP
# Note: this only cleans up the script's own scratch temp directory. It never
# touches anything belonging to an installed Cloudmon component.
# =============================================================================

cleanup() {
    local exit_code=$?
    if [[ -d "${TMP_DIR}" ]]; then
        rm -rf "${TMP_DIR}" 2>/dev/null || true
    fi
    if [[ $exit_code -eq 0 ]]; then
        return 0
    fi
    if [[ "${USAGE_ERROR}" == true ]]; then
        return 0
    fi
    if [[ -z "${LOG_FILE}" ]]; then
        return 0
    fi
    log_error "Script exited with code ${exit_code}. Check log: ${LOG_FILE}"
}

trap cleanup EXIT
trap 'log_error "Unexpected error on line ${LINENO}. Exiting."; exit 1' ERR

# =============================================================================
# LOGGING
# =============================================================================

_init_log() {
    mkdir -p "${LOG_DIR}"
    LOG_FILE="${LOG_DIR}/cloudmon-install-$(date +%Y-%m-%d_%H-%M-%S).log"
    touch "${LOG_FILE}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ===== Cloudmon Installer ${SCRIPT_VERSION} Started =====" >> "${LOG_FILE}"
}

_log_raw() {
    local level="$1"; shift
    local msg="$*"
    [[ -z "${LOG_FILE}" ]] && return 0
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${msg}" >> "${LOG_FILE}"
}

log_info()    { printf "  ${BLUE}%-8s${NC} %s\n"   "[INFO]"  "$*";    _log_raw "INFO"  "$*"; }
log_success() { printf "  ${GREEN}%-8s${NC} %s\n"  "[OK]"    "$*";    _log_raw "OK"    "$*"; }
log_warn()    { printf "  ${YELLOW}%-8s${NC} %s\n" "[WARN]"  "$*";    _log_raw "WARN"  "$*"; }
log_error()   { printf "  ${RED}%-8s${NC} %s\n"    "[ERROR]" "$*" >&2; _log_raw "ERROR" "$*"; }

log_step() {
    echo -e "\n${BOLD}${CYAN}▶ $*${NC}"
    _log_raw "STEP" "$*"
}

# =============================================================================
# SUMMARY
# =============================================================================

summary_add() {
    local component="$1" status="$2" detail="$3"
    SUMMARY_COMPONENT+=("${component}")
    SUMMARY_STATUS+=("${status}")
    SUMMARY_DETAIL+=("${detail}")
    _log_raw "SUMMARY" "component=${component} status=${status} detail=${detail}"
}

# =============================================================================
# USER INTERACTION
# =============================================================================

ask_yn() {
    local prompt="$1"
    local default="${2:-ask}"
    if [[ "${NON_INTERACTIVE}" == true ]]; then
        [[ "${default}" == "yes" ]] && return 0 || return 1
    fi
    local answer
    while true; do
        read -rp "$(echo -e "  ${YELLOW}${prompt} (y/n): ${NC}")" answer
        case "${answer,,}" in
            y|yes) return 0 ;;
            n|no)  return 1 ;;
            *)     echo -e "  ${RED}Please enter y or n.${NC}" ;;
        esac
    done
}

prompt_user() {
    local prompt="$1" var_name="$2" secret="${3:-false}"
    if [[ "${NON_INTERACTIVE}" == true ]]; then
        local current_val
        current_val="$(eval echo "\${${var_name}:-}")"
        if [[ -z "${current_val}" ]]; then
            log_error "Non-interactive mode: required value '${var_name}' not provided."
            exit 1
        fi
        return 0
    fi
    local input
    while true; do
        if [[ "${secret}" == true ]]; then
            read -rsp "$(echo -e "  ${YELLOW}${prompt}: ${NC}")" input; echo ""
        else
            read -rp "$(echo -e "  ${YELLOW}${prompt}: ${NC}")" input
        fi
        if [[ -n "${input}" ]]; then
            printf -v "${var_name}" '%s' "${input}"
            return 0
        fi
        echo -e "  ${RED}This value cannot be empty.${NC}"
    done
}

confirm_continue() {
    local context="$1"
    log_warn "Step failed: ${context}"
    if ask_yn "Continue anyway?" "no"; then
        return 0
    fi
    exit 1
}

# confirm_destructive is used ONLY by uninstall_* functions before they touch
# services, packages, files, or data. In --non-interactive mode it proceeds
# without prompting, since the operator already explicitly requested
# --uninstall on the command line.
confirm_destructive() {
    local what="$1"
    if [[ "${NON_INTERACTIVE}" == true ]]; then
        return 0
    fi
    if ask_yn "This will permanently remove ${what} (services, files, and data). Continue?" "no"; then
        return 0
    fi
    log_warn "Skipped removal of ${what} by user choice."
    return 1
}

# =============================================================================
# SYSTEM CHECKS
# =============================================================================

check_root() {
    if [[ "${EUID}" -ne 0 ]]; then
        log_error "This script must be run as root (use: sudo bash $0 ...)."
        exit 1
    fi
    log_success "Running as root."
}

check_os() {
    log_step "Validating Operating System"
    if [[ ! -f /etc/os-release ]]; then
        log_error "/etc/os-release not found."
        exit 1
    fi
    # shellcheck disable=SC1091
    source /etc/os-release
    local os_id="${NAME:-unknown}"
    local os_version="${VERSION_ID:-unknown}"
    local codename="${VERSION_CODENAME:-unknown}"
    log_info "Detected OS: ${os_id} ${os_version} (${codename})"

    if [[ "${os_id}" != *"Ubuntu"* ]]; then
        log_error "Unsupported OS: ${os_id}. Ubuntu 24.04 is required."
        exit 1
    fi

    if [[ "${os_version}" != "24.04" ]]; then
        log_error "Ubuntu ${os_version} is not supported. Ubuntu 24.04 (noble) is required."
        exit 1
    fi

    UBUNTU_VERSION="${os_version}"
    UBUNTU_CODENAME="${codename}"
    log_success "OS: Ubuntu ${UBUNTU_VERSION} (${UBUNTU_CODENAME})"
}

# =============================================================================
# APT HELPERS
# =============================================================================

wait_for_apt() {
    local timeout=300 waited=0
    while fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
       || fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
       || fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
        (( waited >= timeout )) && { log_error "APT lock timeout."; return 1; }
        log_warn "APT busy. Waiting... (${waited}s/${timeout}s)"
        sleep 5; waited=$((waited + 5))
    done
}

apt_update() {
    wait_for_apt
    log_info "Running apt-get update..."
    apt-get update -y >> "${LOG_FILE}" 2>&1 || { log_error "apt-get update failed."; return 1; }
    log_success "apt-get update done."
}

apt_install() {
    wait_for_apt
    log_info "Installing: $*"
    DEBIAN_FRONTEND=noninteractive apt-get install -y "$@" >> "${LOG_FILE}" 2>&1 \
        || { log_error "Failed to install: $*"; return 1; }
    log_success "Installed: $*"
}

# =============================================================================
# JQ
# =============================================================================

install_jq() {
    command -v jq &>/dev/null && { log_info "jq already installed."; return 0; }
    apt_update
    apt_install jq
}

check_dependencies() {

log_step "Checking dependencies"

deps=(
curl
wget
jq
awk
sed
grep
tee
hostname
ip
fuser
gpg
systemctl
tar
gzip
find
)

missing=0

for dep in "${deps[@]}"
do

    if command -v "$dep" >/dev/null 2>&1
    then
        log_success "$dep found"
    else
        log_warn "$dep missing"
        missing=1
    fi

done

return $missing

}

# =============================================================================
# PACKAGE URL / DOWNLOAD URLS
# Component installers are downloaded directly from the user-supplied
# PACKAGE_URL rather than a remote version manifest. Expected layout:
#   <PACKAGE_URL>/controller.linux.x86_64.run
#   <PACKAGE_URL>/collector.linux.x86_64.run
#   <PACKAGE_URL>/probe.linux.x86_64.run
# =============================================================================

compute_download_urls() {
    log_info "Fetching package manifest: ${PACKAGE_URL}"
    local manifest
    manifest="$(curl -fsSL "${PACKAGE_URL}")" || {
        log_error "Unable to download package manifest."
        return 1
    }
    URL_CONTROLLER="$(echo "${manifest}" | jq -r '.urls["controller.linux.x86_64"]')"
    URL_COLLECTOR="$(echo "${manifest}" | jq -r '.urls["collector.linux.x86_64"]')"
    URL_PROBE="$(echo "${manifest}" | jq -r '.urls["probe.linux.x86_64"]')"
    [[ "${URL_CONTROLLER}" == "null" || -z "${URL_CONTROLLER}" ]] && {
        log_error "Controller URL missing in manifest."
        return 1
    }
    [[ "${URL_COLLECTOR}" == "null" || -z "${URL_COLLECTOR}" ]] && {
        log_error "Collector URL missing in manifest."
        return 1
    }
    [[ "${URL_PROBE}" == "null" || -z "${URL_PROBE}" ]] && {
        log_error "Probe URL missing in manifest."
        return 1
    }
    log_info "Controller URL : ${URL_CONTROLLER}"
    log_info "Collector URL  : ${URL_COLLECTOR}"
    log_info "Probe URL      : ${URL_PROBE}"
}

# Prompts for PACKAGE_URL only if it hasn't already been supplied/collected,
# then (re)computes the component download URLs from it.
ensure_package_url() {
    [[ -z "${PACKAGE_URL}" ]] && prompt_user "Package URL" "PACKAGE_URL"
    compute_download_urls
}

# =============================================================================
# NODE.JS (v24)
# Idempotent: only installs if a suitable version is not already present.
# =============================================================================

install_nodejs() {
    log_step "Node.js ${NODEJS_MAJOR_REQUIRED} Installation"
    if command -v node &>/dev/null; then
        local current_major
        current_major="$(node -e 'process.stdout.write(process.versions.node.split(".")[0])')"
        if [[ "${current_major}" -ge "${NODEJS_MAJOR_REQUIRED}" ]]; then
            log_success "Node.js already installed: $(node -v)"
            summary_add "Node.js" "SKIPPED" "$(node -v)"
            return 0
        fi
        log_warn "Node.js $(node -v) found, below required v${NODEJS_MAJOR_REQUIRED}."
    else
        log_warn "Node.js not found."
    fi

    if ! ask_yn "Node.js v${NODEJS_MAJOR_REQUIRED}+ is required. Attempt to install it now?" "no"; then
        log_error "Node.js v${NODEJS_MAJOR_REQUIRED}+ is required but installation was declined."
        log_error "Please install Node.js v${NODEJS_MAJOR_REQUIRED}+ manually, then re-run this script."
        summary_add "Node.js" "FAILED" "Installation declined by user"
        exit 1
    fi

    wait_for_apt
    curl -fsSL "https://deb.nodesource.com/setup_${NODEJS_MAJOR_REQUIRED}.x" \
        | bash - >> "${LOG_FILE}" 2>&1 \
        || { log_error "NodeSource setup failed."; summary_add "Node.js" "FAILED" "NodeSource setup failed"; confirm_continue "Node.js setup"; return 1; }
    apt_install nodejs
    log_success "Node.js installed: $(node -v)"
    summary_add "Node.js" "SUCCESS" "$(node -v)"
}

# =============================================================================
# NGINX
# install_nginx()   — idempotent. Verifies an existing installation and skips
#                     it untouched, or performs a fresh install via
#                     install_nginx_clean() if nginx is not present at all.
# uninstall_nginx() — the only function that stops, purges, and deletes nginx.
# Uses the nginx.org stable repo with the recommended GPG key method for
# Ubuntu 24.04.
# =============================================================================

nginx_is_installed() { command -v nginx &>/dev/null; }

nginx_version_ok() {
    local nginx_version nginx_minor
    nginx_version="$(nginx -v 2>&1 | grep -oP '(?<=nginx/)[0-9]+\.[0-9]+\.[0-9]+' || true)"
    nginx_minor="$(echo "${nginx_version}" | cut -d. -f2)"
    [[ -n "${nginx_minor}" && "${nginx_minor}" -gt "${NGINX_MINOR_REQUIRED}" && -f /etc/nginx/nginx.conf ]]
}

# Rather than deleting nginx's stock default site (which would violate the
# "never delete configuration files during install" rule), we rename it out
# of the way so it can be restored later by uninstall/rollback.
disable_nginx_default_site() {
    if [[ -f /etc/nginx/sites-enabled/default ]]; then
        mv /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/default.disabled-by-cloudmon 2>/dev/null || true
    fi
    if [[ -f /etc/nginx/conf.d/default.conf ]]; then
        mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.disabled-by-cloudmon 2>/dev/null || true
    fi
}

restore_nginx_default_site() {
    if [[ -f /etc/nginx/sites-enabled/default.disabled-by-cloudmon ]]; then
        mv /etc/nginx/sites-enabled/default.disabled-by-cloudmon /etc/nginx/sites-enabled/default 2>/dev/null || true
    fi
    if [[ -f /etc/nginx/conf.d/default.conf.disabled-by-cloudmon ]]; then
        mv /etc/nginx/conf.d/default.conf.disabled-by-cloudmon /etc/nginx/conf.d/default.conf 2>/dev/null || true
    fi
}

install_nginx_clean() {
    log_step "Installing Nginx (nginx.org stable repository)"

    apt_update
    apt_install curl gnupg2 ca-certificates lsb-release ubuntu-keyring

    curl -fsSL "${NGINX_SIGNING_KEY_URL}" \
        | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg \
        || { log_error "Nginx GPG key import failed."; return 1; }

    cat > /etc/apt/sources.list.d/nginx.list << EOF
deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu ${UBUNTU_CODENAME} nginx
EOF

    cat > /etc/apt/preferences.d/99nginx << 'EOF'
Package: *
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900
EOF

    apt_update
    apt_install nginx || { log_error "Nginx package installation failed."; return 1; }

    log_success "Nginx installed from nginx.org stable repository."
}

install_nginx() {
    log_step "Nginx Installation"

    if nginx_is_installed && nginx_version_ok; then
        local nginx_version
        nginx_version="$(nginx -v 2>&1 | grep -oP '(?<=nginx/)[0-9]+\.[0-9]+\.[0-9]+' || true)"
        log_info "Existing Nginx installation detected: nginx/${nginx_version}"

        if ! nginx -t &>/dev/null; then
            log_warn "Existing Nginx configuration does not pass 'nginx -t'."
            if ! ask_yn "Continue without modifying the existing Nginx installation?" "no"; then
                log_error "Aborting. Fix Nginx manually, or use '--uninstall --controller' to remove Cloudmon's integration."
                summary_add "Nginx" "FAILED" "Existing config test failed"
                exit 1
            fi
            summary_add "Nginx" "WARN" "Existing config test failed — left untouched"
            return 0
        fi

        if ! systemctl is-active nginx &>/dev/null; then
            log_info "Starting existing Nginx service..."
            systemctl enable nginx >> "${LOG_FILE}" 2>&1 || true
            systemctl start  nginx >> "${LOG_FILE}" 2>&1 || true
        fi

        if systemctl is-active nginx &>/dev/null; then
            log_success "Existing Nginx installation is healthy and running."
            summary_add "Nginx" "SKIPPED" "Existing installation detected (nginx/${nginx_version})"
        else
            log_warn "Existing Nginx installation could not be started."
            summary_add "Nginx" "WARN" "Existing installation present but not running"
        fi
        return 0
    fi

    if nginx_is_installed; then
        log_warn "Nginx is installed but does not meet the required version (> 1.${NGINX_MINOR_REQUIRED}) or is missing nginx.conf."
        if ! ask_yn "Continue without modifying the existing Nginx installation?" "no"; then
            log_error "Aborting. Use '--uninstall --controller' (or remove Nginx manually) before re-running install."
            summary_add "Nginx" "FAILED" "Existing installation does not meet requirements"
            exit 1
        fi
        summary_add "Nginx" "WARN" "Existing installation does not meet requirements — left untouched"
        return 0
    fi

    log_warn "Nginx not found."
    if ! ask_yn "Nginx above version 1.${NGINX_MINOR_REQUIRED} is required. Attempt to install it now?" "no"; then
        log_error "Nginx above version 1.${NGINX_MINOR_REQUIRED} is required but installation was declined."
        log_error "Please install Nginx manually, then re-run this script."
        summary_add "Nginx" "FAILED" "Installation declined by user"
        exit 1
    fi

    install_nginx_clean || { log_error "Nginx installation failed."; summary_add "Nginx" "FAILED" "Installation failed"; confirm_continue "Nginx installation"; return 1; }

    if [[ ! -f /etc/nginx/nginx.conf ]]; then
        log_error "/etc/nginx/nginx.conf is missing after installation."
        log_error "This points to something outside apt interfering (e.g. a config-management tool, a container overlay, or a mount masking /etc/nginx)."
        summary_add "Nginx" "FAILED" "nginx.conf missing after install"
        confirm_continue "Nginx config missing"
        return 1
    fi

    local ngx_test_output=""
    if ! ngx_test_output="$(nginx -t 2>&1)"; then
        log_error "nginx -t (config test) failed:"
        while IFS= read -r line; do log_error "  ${line}"; done <<< "${ngx_test_output}"
        summary_add "Nginx" "FAILED" "Config test failed after install"
        confirm_continue "Nginx config test"
        return 1
    fi

    systemctl enable nginx >> "${LOG_FILE}" 2>&1 || true
    systemctl start  nginx >> "${LOG_FILE}" 2>&1 || true

    if systemctl is-active nginx &>/dev/null; then
        log_success "Nginx installed and running."
        summary_add "Nginx" "SUCCESS" "Installed and running"
    else
        log_error "Nginx failed to start. Diagnostics:"
        {
            echo "----- systemctl status nginx -----"
            systemctl status nginx --no-pager -l
            echo "----- ss -ltnp (port 80/443 check) -----"
            ss -ltnp 2>/dev/null | grep -E ':80 |:443 '
        } >> "${LOG_FILE}" 2>&1 || true
        local ngx_status_output
        ngx_status_output="$(systemctl status nginx --no-pager -l 2>&1 || true)"
        while IFS= read -r line; do log_error "  ${line}"; done <<< "${ngx_status_output}"
        log_error "  Also check what's listening on ports 80/443: ss -ltnp | grep -E ':80|:443'"
        summary_add "Nginx" "FAILED" "Service startup failed — see log for diagnostics"
        confirm_continue "Nginx startup"
    fi
}

uninstall_nginx() {
    log_step "Nginx Removal"

    if ! nginx_is_installed && [[ ! -d /etc/nginx ]]; then
        log_info "Nginx is not installed. Nothing to do."
        summary_add "Nginx" "SKIPPED" "Not installed"
        return 0
    fi

    log_info "Stopping and disabling Nginx service..."
    systemctl stop    nginx >> "${LOG_FILE}" 2>&1 || true
    systemctl disable nginx >> "${LOG_FILE}" 2>&1 || true

    log_info "Purging Nginx package..."
    DEBIAN_FRONTEND=noninteractive apt-get purge -y nginx nginx-common >> "${LOG_FILE}" 2>&1 || true

    log_info "Removing Nginx configuration and repository files..."
    rm -rf /etc/nginx
    rm -f /etc/apt/sources.list.d/nginx.list
    rm -f /etc/apt/preferences.d/99nginx
    rm -f /usr/share/keyrings/nginx-archive-keyring.gpg

    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    log_success "Nginx removed."
    summary_add "Nginx" "SUCCESS" "Removed"
}

# =============================================================================
# REDIS
# install_redis()   — idempotent. Verifies an existing installation/service
#                     and skips it untouched, or installs redis-server via
#                     apt if not present at all.
# uninstall_redis() — the only function that stops, purges, and deletes
#                     Redis (package, data, config, logs).
# =============================================================================

redis_is_installed() { command -v redis-server &>/dev/null; }

install_redis() {
    log_step "Redis Installation"

    if redis_is_installed; then
        log_info "Existing Redis installation detected: $(redis-server --version 2>/dev/null | head -1)"

        if ! systemctl is-active redis-server &>/dev/null; then
            log_info "Starting existing Redis service..."
            systemctl enable redis-server >> "${LOG_FILE}" 2>&1 || true
            systemctl start  redis-server >> "${LOG_FILE}" 2>&1 || true
        fi

        if systemctl is-active redis-server &>/dev/null; then
            log_success "Existing Redis installation is healthy and running."
            summary_add "Redis" "SKIPPED" "Existing installation detected"
        else
            log_warn "Existing Redis installation could not be started."
            summary_add "Redis" "WARN" "Existing installation present but not running"
        fi
        return 0
    fi

    log_warn "Redis not found."
    if ! ask_yn "Redis is required. Attempt to install it now?" "no"; then
        log_error "Redis is required but installation was declined."
        log_error "Please install Redis manually, then re-run this script."
        summary_add "Redis" "FAILED" "Installation declined by user"
        exit 1
    fi

    apt_update
    apt_install redis-server \
        || { log_error "Redis installation failed."; summary_add "Redis" "FAILED" "Installation failed"; confirm_continue "Redis installation"; return 1; }

    systemctl enable redis-server >> "${LOG_FILE}" 2>&1 || true
    systemctl start  redis-server >> "${LOG_FILE}" 2>&1 || true

    if systemctl is-active redis-server &>/dev/null; then
        log_success "Redis installed and running."
        summary_add "Redis" "SUCCESS" "Installed and running"
    else
        log_error "Redis failed to start. Check: systemctl status redis-server"
        summary_add "Redis" "FAILED" "Service startup failed"
        confirm_continue "Redis startup"
    fi
}

uninstall_redis() {
    log_step "Redis Removal"

    if ! redis_is_installed && [[ ! -d /etc/redis ]]; then
        log_info "Redis is not installed. Nothing to do."
        summary_add "Redis" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "Redis"; then
        summary_add "Redis" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    log_info "Stopping and disabling Redis service..."
    systemctl stop    redis-server >> "${LOG_FILE}" 2>&1 || true
    systemctl disable redis-server >> "${LOG_FILE}" 2>&1 || true

    log_info "Purging Redis package..."
    DEBIAN_FRONTEND=noninteractive apt-get purge -y redis-server redis-tools redis-sentinel >> "${LOG_FILE}" 2>&1 || true
    apt-get autoremove -y >> "${LOG_FILE}" 2>&1 || true

    log_info "Removing Redis configuration, data, and logs..."
    rm -rf /etc/redis
    rm -rf /var/lib/redis
    rm -rf /var/log/redis

    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    log_success "Redis removed."
    summary_add "Redis" "SUCCESS" "Removed"
}

# =============================================================================
# MONGODB
# install_mongodb()   — idempotent. If an existing installation is detected,
#                       verifies service/mongosh/db-user health and skips.
#                       Never stops, purges, or drops data.
# uninstall_mongodb() — the only function that stops, purges, and deletes
#                       MongoDB data, config, repo, and keyring.
# =============================================================================

mongodb_is_installed() {
    command -v mongod &>/dev/null || dpkg -l 2>/dev/null | grep -q '^ii\s\+mongodb-org\b'
}

mongodb_user_healthy() {
    command -v mongosh &>/dev/null || return 1
    mongosh --quiet "mongodb://${LOCAL_MONGO_DB_USER}:${LOCAL_MONGO_DB_PASS}@127.0.0.1:27017/${CLOUDMON_DB_NAME}?authSource=${CLOUDMON_DB_NAME}" \
        --eval "db.runCommand({ ping: 1 }).ok" 2>>"${LOG_FILE}" | grep -q '^1$'
}

# Ensures the MongoDB apt repository + signing key are configured. Shared by
# install_mongodb() and install_mongosh() so the mongosh-only path doesn't
# need to install the full mongodb-org server package.
ensure_mongodb_repo() {
    wait_for_apt
    apt_install gnupg curl

    if [[ ! -f "/usr/share/keyrings/mongodb-server-${MONGODB_VERSION}.gpg" ]]; then
        curl -fsSL "https://www.mongodb.org/static/pgp/server-${MONGODB_VERSION}.asc" \
            | gpg -o "/usr/share/keyrings/mongodb-server-${MONGODB_VERSION}.gpg" \
                  --dearmor >> "${LOG_FILE}" 2>&1 \
            || { log_error "MongoDB GPG key import failed."; return 1; }
    fi

    if [[ ! -f "/etc/apt/sources.list.d/mongodb-org-${MONGODB_VERSION}.list" ]]; then
        echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-${MONGODB_VERSION}.gpg ] \
https://repo.mongodb.org/apt/ubuntu ${UBUNTU_CODENAME}/mongodb-org/${MONGODB_VERSION} multiverse" \
            | tee "/etc/apt/sources.list.d/mongodb-org-${MONGODB_VERSION}.list" >> "${LOG_FILE}" 2>&1
    fi

    apt_update
}

install_mongodb() {
    log_step "MongoDB ${MONGODB_VERSION} Installation"

    if mongodb_is_installed; then
        log_info "Existing MongoDB installation detected."

        local healthy=true

        if ! systemctl is-active mongod &>/dev/null; then
            log_warn "mongod service is not running. Attempting to start it..."
            systemctl start mongod >> "${LOG_FILE}" 2>&1 || true
            sleep 3
            systemctl is-active mongod &>/dev/null || { log_warn "mongod could not be started."; healthy=false; }
        fi

        if ! command -v mongosh &>/dev/null; then
            log_warn "mongosh is not available."
            healthy=false
        fi

        if [[ "${healthy}" == true ]] && ! mongodb_user_healthy; then
            log_warn "Could not verify the Cloudmon database/user (${CLOUDMON_DB_NAME}/${LOCAL_MONGO_DB_USER})."
            healthy=false
        fi

        if [[ "${healthy}" == true ]]; then
            log_success "Existing MongoDB installation is healthy."
            summary_add "MongoDB" "SKIPPED" "Existing installation detected"
            return 0
        fi

        log_warn "Existing MongoDB installation appears unhealthy."
        if ! ask_yn "Continue without modifying the existing installation?" "no"; then
            log_error "Aborting. Use '--uninstall --mongodb' to remove it first, then re-run install."
            summary_add "MongoDB" "FAILED" "Unhealthy existing installation"
            exit 1
        fi
        summary_add "MongoDB" "WARN" "Existing installation unhealthy — left untouched"
        return 0
    fi

    ensure_mongodb_repo \
        || { log_error "MongoDB repository setup failed."; summary_add "MongoDB" "FAILED" "Repository setup failed"; confirm_continue "MongoDB repository"; return 1; }

    apt_install mongodb-org

    # Verify mongosh is available — required for user creation
    if ! command -v mongosh &>/dev/null; then
        log_error "mongosh not found after MongoDB install."
        summary_add "MongoDB" "FAILED" "mongosh not available"
        confirm_continue "mongosh missing"
        return 1
    fi

    # Start MongoDB WITHOUT authorization first — required for user creation
    log_info "Starting MongoDB (no auth) for user setup..."

    local mongod_conf="/etc/mongod.conf"
    sed -i '/authorization:/d' "${mongod_conf}" 2>/dev/null || true
    sed -i '/^security:/d'     "${mongod_conf}" 2>/dev/null || true

    systemctl enable mongod >> "${LOG_FILE}" 2>&1 || true
    systemctl start  mongod >> "${LOG_FILE}" 2>&1 || true
    sleep 3

    if ! systemctl is-active mongod &>/dev/null; then
        log_error "MongoDB failed to start."
        summary_add "MongoDB" "FAILED" "Service startup failed"
        confirm_continue "MongoDB startup"
        return 1
    fi
    log_success "MongoDB running (no auth)."

    # Create cloudmon DB user — per official guide
    log_info "Creating MongoDB user '${LOCAL_MONGO_DB_USER}'..."
    mongosh --quiet << EOF >> "${LOG_FILE}" 2>&1
use ${CLOUDMON_DB_NAME}

if (!db.getUser("${LOCAL_MONGO_DB_USER}")) {

db.createUser({

user: "${LOCAL_MONGO_DB_USER}",

pwd: "${LOCAL_MONGO_DB_PASS}",

roles: [
{
role:"readWrite",
db:"${CLOUDMON_DB_NAME}"
}
]

})

} else {

print("User already exists")

}
EOF

    if [[ $? -ne 0 ]]; then
        log_error "MongoDB user creation failed."
        summary_add "MongoDB" "FAILED" "User creation failed"
        confirm_continue "MongoDB user"
        return 1
    fi
    log_success "MongoDB user '${LOCAL_MONGO_DB_USER}' created."

    # Enable authorization — per official guide
    log_info "Enabling MongoDB authorization..."
    printf '\nsecurity:\n  authorization: enabled\n' >> "${mongod_conf}"
    log_info "Authorization added to mongod.conf."

    systemctl restart mongod >> "${LOG_FILE}" 2>&1
    sleep 3

    if systemctl is-active mongod &>/dev/null; then
        log_success "MongoDB running with authorization enabled."
        summary_add "MongoDB" "SUCCESS" "Running with auth"
    else
        log_error "MongoDB failed to restart after enabling authorization."
        summary_add "MongoDB" "FAILED" "Restart after auth failed"
        confirm_continue "MongoDB restart"
    fi
}

uninstall_mongodb() {
    log_step "MongoDB Removal"

    if ! mongodb_is_installed; then
        log_info "MongoDB is not installed. Nothing to do."
        summary_add "MongoDB" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "MongoDB (this will delete the ${CLOUDMON_DB_NAME} database and all its data)"; then
        summary_add "MongoDB" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    if command -v mongosh &>/dev/null && systemctl is-active mongod &>/dev/null; then
        log_info "Dropping ${CLOUDMON_DB_NAME} database if present..."
        mongosh --quiet --eval "use ${CLOUDMON_DB_NAME}; db.dropDatabase()" >> "${LOG_FILE}" 2>&1 || true
    fi

    log_info "Stopping MongoDB service..."
    systemctl stop    mongod >> "${LOG_FILE}" 2>&1 || true
    systemctl disable mongod >> "${LOG_FILE}" 2>&1 || true

    log_info "Purging MongoDB packages..."
    DEBIAN_FRONTEND=noninteractive apt-get purge -y mongodb-org* >> "${LOG_FILE}" 2>&1 || true

    log_info "Removing MongoDB data, logs, and configuration..."
    rm -rf /var/lib/mongodb
    rm -rf /var/log/mongodb
    rm -f /etc/mongod.conf
    rm -f /etc/systemd/system/mongod.service
    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    log_info "Removing MongoDB apt repository and keyring..."
    rm -f /etc/apt/sources.list.d/mongodb-org-*.list
    rm -f /usr/share/keyrings/mongodb-server-*.gpg

    log_success "MongoDB removed."
    summary_add "MongoDB" "SUCCESS" "Removed"
}

# =============================================================================
# MONGOSH (MongoDB Shell)
# install_mongosh()   — idempotent, standalone. Installs only the mongosh
#                       client (not the full mongodb-org server) via the same
#                       MongoDB apt repository used by install_mongodb(). If
#                       mongosh is already on the system (e.g. pulled in as a
#                       dependency of mongodb-org) it is detected and skipped.
# uninstall_mongosh() — the only function that purges the mongosh package.
#                       Does not touch the MongoDB apt repository/keyring or
#                       any running mongod service/data, since those are
#                       owned by install_mongodb()/uninstall_mongodb().
# =============================================================================

mongosh_is_installed() { command -v mongosh &>/dev/null; }

install_mongosh() {
    log_step "MongoDB Shell (mongosh) Installation"

    if mongosh_is_installed; then
        log_success "mongosh already installed: $(mongosh --version 2>/dev/null | head -1)"
        summary_add "mongosh" "SKIPPED" "$(mongosh --version 2>/dev/null | head -1)"
        return 0
    fi

    log_warn "mongosh not found."
    if ! ask_yn "mongosh (MongoDB Shell) is required. Attempt to install it now?" "no"; then
        log_error "mongosh is required but installation was declined."
        log_error "Please install mongosh manually, then re-run this script."
        summary_add "mongosh" "FAILED" "Installation declined by user"
        exit 1
    fi

    ensure_mongodb_repo \
        || { log_error "MongoDB repository setup failed."; summary_add "mongosh" "FAILED" "Repository setup failed"; confirm_continue "mongosh repository"; return 1; }

    apt_install mongodb-mongosh \
        || { log_error "mongosh installation failed."; summary_add "mongosh" "FAILED" "Installation failed"; confirm_continue "mongosh installation"; return 1; }

    log_success "mongosh installed: $(mongosh --version 2>/dev/null | head -1)"
    summary_add "mongosh" "SUCCESS" "$(mongosh --version 2>/dev/null | head -1)"
}

uninstall_mongosh() {
    log_step "MongoDB Shell (mongosh) Removal"

    if ! mongosh_is_installed; then
        log_info "mongosh is not installed. Nothing to do."
        summary_add "mongosh" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "mongosh (MongoDB Shell)"; then
        summary_add "mongosh" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    log_info "Purging mongosh package..."
    DEBIAN_FRONTEND=noninteractive apt-get purge -y mongodb-mongosh >> "${LOG_FILE}" 2>&1 || true
    apt-get autoremove -y >> "${LOG_FILE}" 2>&1 || true

    log_success "mongosh removed."
    summary_add "mongosh" "SUCCESS" "Removed"
}

# =============================================================================
# COLLECT CONTROLLER INPUTS
# Prompts for the database connection details and the PACKAGE_URL that get
# written into the controller's .env file. Only called on a fresh install.
# =============================================================================

collect_controller_inputs() {
    log_step "Collecting Controller Configuration"

    if [[ "${DO_MONGODB}" == true ]]; then
        # MongoDB is being installed locally as part of this same run
        # (--mongodb or --all), so point the Controller at it automatically
        # instead of prompting for DB details that are already known.
        [[ -z "${DB_HOST}" ]] && DB_HOST="127.0.0.1"
        [[ -z "${DB_USER}" ]] && DB_USER="${LOCAL_MONGO_DB_USER}"
        [[ -z "${DB_PASS}" ]] && DB_PASS="${LOCAL_MONGO_DB_PASS}"
        log_info "Using local MongoDB installed in this run: ${DB_HOST} (user: ${DB_USER})"
    else
        [[ -z "${DB_HOST}" ]] && prompt_user "Database IP/Hostname" "DB_HOST"
        [[ -z "${DB_USER}" ]] && prompt_user "Database Username" "DB_USER"
        [[ -z "${DB_PASS}" ]] && prompt_user "Database Password" "DB_PASS" "true"
    fi

    [[ -z "${PACKAGE_URL}" ]] && prompt_user "Package URL" "PACKAGE_URL"

    log_success "Controller configuration collected."
}

# =============================================================================
# CONTROLLER
# install_controller()   — idempotent. Detects an existing installation,
#                          verifies it, and skips if healthy. Never deletes
#                          or overwrites an existing installation.
# uninstall_controller() — the only function that stops services and removes
#                          the controller installation, systemd units, and
#                          Cloudmon's Nginx integration.
# =============================================================================

controller_is_installed() { [[ -d "${CLOUDMON_SERVER_DIR}" ]]; }

verify_controller_health() {
    local ok=true
    if [[ ! -f "${CLOUDMON_SERVER_DIR}/.env" ]]; then
        log_warn "Controller .env file missing at ${CLOUDMON_SERVER_DIR}/.env"
        ok=false
    fi
    local running
    running="$(systemctl list-units --type=service --state=running 2>/dev/null \
               | grep -c 'cloudmon-controller@' || true)"
    if [[ "${running}" -eq 0 ]]; then
        log_warn "No running cloudmon-controller@ units detected."
        ok=false
    fi
    [[ "${ok}" == true ]]
}

install_controller() {
    log_step "Cloudmon Controller Installation"

    if controller_is_installed; then
        log_info "Existing controller installation detected at ${CLOUDMON_SERVER_DIR}."
        if verify_controller_health; then
            log_success "Existing controller installation looks healthy. Skipping installation."
            summary_add "Controller" "SKIPPED" "Existing installation detected"
            return 0
        fi

        log_warn "Existing controller installation detected but appears unhealthy."
        if ! ask_yn "Continue without modifying the existing installation?" "no"; then
            log_error "Aborting. Use '--uninstall --controller' to remove it first, then re-run install."
            summary_add "Controller" "FAILED" "Unhealthy existing installation"
            exit 1
        fi
        summary_add "Controller" "WARN" "Existing installation unhealthy — left untouched"
        return 0
    fi

    collect_controller_inputs
    compute_download_urls || return 1
    install_nodejs || true
    install_nginx || true

    local installer="${TMP_DIR}/cloudmon-controller.run"
    log_info "Downloading controller package..."
    curl -fsSL "${URL_CONTROLLER}" -o "${installer}" \
        || { log_error "Controller download failed."; summary_add "Controller" "FAILED" "Download failed"; confirm_continue "Controller download"; return 1; }

    chmod +x "${installer}"
    log_info "Running controller installer..."
    "${installer}" >> "${LOG_FILE}" 2>&1 \
        || { log_error "Controller installer failed."; summary_add "Controller" "FAILED" "Installer failed"; confirm_continue "Controller install"; return 1; }

    log_success "Controller installer completed."

    # Redis: the controller .run installer above may already install Redis
    # itself, so this runs afterward — if Redis is already present/healthy,
    # install_redis() detects that and skips it; if not, it installs it here.
    install_redis || true

    # Configure .env — per official guide
    configure_controller_env

    # Nginx configuration — per official guide
    configure_nginx_configuration || true

    # Reload systemd and start all controller units
    log_info "Reloading systemd and starting controller services..."
    systemctl daemon-reload >> "${LOG_FILE}" 2>&1
    systemctl restart 'cloudmon-controller@*' >> "${LOG_FILE}" 2>&1 || true
    sleep 5

    local running
    running="$(systemctl list-units --type=service --state=running 2>/dev/null \
               | grep -c 'cloudmon-controller@' || true)"

    if [[ "${running}" -gt 0 ]]; then
        log_success "Controller running (${running} units active)."
        summary_add "Controller" "SUCCESS" "${running} units running"
    else
        log_warn "Controller units not detected. Check: systemctl list-units | grep cloudmon-controller"
        summary_add "Controller" "WARN" "Verify manually"
    fi
}

uninstall_controller() {
    log_step "Cloudmon Controller Removal"

    local has_units=false
    systemctl list-units --type=service --all 2>/dev/null | grep -q 'cloudmon-controller' && has_units=true

    local nginx_present=false
    if command -v nginx &>/dev/null || [[ -d /etc/nginx ]]; then
        nginx_present=true
    fi

    local redis_present=false
    if command -v redis-server &>/dev/null || [[ -d /etc/redis ]]; then
        redis_present=true
    fi

    # Nothing to do only if there's no controller app AND no nginx/redis
    # (these can be present even if the controller app install itself failed
    # partway, e.g. a package download error after nginx/redis were already
    # installed).
    if [[ "${has_units}" == false && ! -d "${CLOUDMON_SERVER_DIR}" && "${nginx_present}" == false && "${redis_present}" == false ]]; then
        log_info "Controller is not installed. Nothing to do."
        summary_add "Controller" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "the Cloudmon Controller"; then
        summary_add "Controller" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    log_info "Stopping and disabling controller services..."
    systemctl stop    'cloudmon-controller@*' >> "${LOG_FILE}" 2>&1 || true
    systemctl disable 'cloudmon-controller@*' >> "${LOG_FILE}" 2>&1 || true

    if [[ -d "${CLOUDMON_SERVER_DIR}" ]]; then
        log_info "Removing controller installation at ${CLOUDMON_SERVER_DIR}..."
        rm -rf "${CLOUDMON_SERVER_DIR}"
    fi

    rm -f /etc/systemd/system/cloudmon-controller@.service
    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    if [[ -L /etc/nginx/conf.d/cloudmon.conf || -f /etc/nginx/conf.d/cloudmon.conf ]]; then
        log_info "Removing Cloudmon Nginx configuration link..."
        rm -f /etc/nginx/conf.d/cloudmon.conf
        restore_nginx_default_site
        if command -v nginx &>/dev/null && systemctl is-active nginx &>/dev/null; then
            systemctl reload nginx >> "${LOG_FILE}" 2>&1 || true
        fi
    fi

    # Nginx is installed by --controller (see install_controller), so removing
    # the Controller also removes Nginx: stop service, purge package, delete
    # /etc/nginx, repo, and keyring.
    if command -v nginx &>/dev/null || [[ -d /etc/nginx ]]; then
        log_info "Removing Nginx (installed as part of the Controller)..."
        uninstall_nginx
    fi

    # Redis is installed by --controller (see install_controller), so removing
    # the Controller also removes Redis: stop service, purge package, delete
    # config/data/logs. (uninstall_redis() is idempotent, so this is also
    # safe to run if --redis was uninstalled separately in the same run.)
    if command -v redis-server &>/dev/null || [[ -d /etc/redis ]]; then
        log_info "Removing Redis (installed as part of the Controller)..."
        uninstall_redis
    fi

    log_success "Controller removed."
    summary_add "Controller" "SUCCESS" "Removed"
}

# =============================================================================
# CONFIGURE CONTROLLER .env
# Per official guide: DATABASE_URL, DATABASE, PACKAGE_URL
# DB host/user/pass and PACKAGE_URL are collected from the user beforehand
# (see collect_controller_inputs). Note: No quotes around values — avoids
# dotenv parser issues.
# =============================================================================

configure_controller_env() {
    log_step "Configuring Controller .env"

    local env_file="${CLOUDMON_SERVER_DIR}/.env"
    local db_host="${DB_HOST}"
    [[ "${db_host}" != *:* ]] && db_host="${db_host}:27017"
    local db_url="mongodb://${DB_USER}:${DB_PASS}@${db_host}"

    if [[ ! -d "${CLOUDMON_SERVER_DIR}" ]]; then
        log_warn "Controller directory not found at ${CLOUDMON_SERVER_DIR}. Skipping .env config."
        return 0
    fi

    cat > "${env_file}" << EOF
DATABASE_URL="${db_url}"
DATABASE="${CLOUDMON_DB_NAME}"
PACKAGE_URL="${PACKAGE_URL}"
EOF

    log_success ".env written at ${env_file}"
    log_info "DATABASE_HOST : ${DB_HOST}"
    log_info "DATABASE      : ${CLOUDMON_DB_NAME}"
    log_info "PACKAGE_URL   : ${PACKAGE_URL}"
    # Note: DATABASE_URL not logged to avoid credential exposure in logs
}

# =============================================================================
# NGINX CONFIGURATION
# Links the cloudmon-provided nginx config and disables (never deletes) the
# stock default site so it doesn't conflict with Cloudmon's vhost.
# =============================================================================

configure_nginx_configuration() {
    log_step "Nginx Configuration"

    if [[ ! -f "${CLOUDMON_NGINX_CONF}" ]]; then
        log_warn "Cloudmon nginx config not found at ${CLOUDMON_NGINX_CONF}. Skipping."
        return 0
    fi

    disable_nginx_default_site

    local target="/etc/nginx/conf.d/cloudmon.conf"
    local src_real dst_real
    src_real="$(readlink -f "${CLOUDMON_NGINX_CONF}" 2>/dev/null || echo "${CLOUDMON_NGINX_CONF}")"
    dst_real="$(readlink -f "${target}" 2>/dev/null || echo "${target}")"

    if [[ "${src_real}" == "${dst_real}" ]]; then
        log_info "Cloudmon nginx config is already in place at ${target}."
    else
        ln -sf "${CLOUDMON_NGINX_CONF}" "${target}" \
            || { log_error "Failed to link Nginx config."; summary_add "Nginx Configuration" "FAILED" "Symlink failed"; confirm_continue "Nginx config link"; return 1; }
        log_info "Linked cloudmon nginx config."
    fi

    if nginx -t >> "${LOG_FILE}" 2>&1; then
        systemctl reload nginx >> "${LOG_FILE}" 2>&1
        log_success "Nginx reloaded with Cloudmon configuration."
        summary_add "Nginx Configuration" "SUCCESS" "Configured and active"
    else
        log_error "Nginx config test failed. Run: nginx -t"
        summary_add "Nginx Configuration" "FAILED" "Config test failed"
        confirm_continue "Nginx config"
    fi
}

# =============================================================================
# COLLECTOR DEPENDENCIES
# Per official guide: libpcap-dev, nmap
# =============================================================================

install_collector_deps() {
    log_step "Installing Collector/Probe dependencies (libpcap-dev, nmap)"
    apt_update
    apt_install libpcap-dev nmap
    log_success "Dependencies installed."
}

# =============================================================================
# COLLECTOR
# install_collector()   — idempotent. Detects an existing installation and
#                          skips it if healthy. Always ensures the Probe is
#                          checked/installed afterward (Probe is itself
#                          idempotent).
# uninstall_collector() — the only function that stops services and removes
#                          the collector installation and systemd units.
# =============================================================================

collector_is_installed() { [[ -d "${CLOUDMON_COLLECTOR_DIR}" ]]; }

verify_collector_health() {
    local running
    running="$(systemctl list-units --type=service --state=running 2>/dev/null \
               | grep -c 'cloudmon-collector@' || true)"
    [[ "${running}" -gt 0 ]]
}

install_collector() {
    log_step "Cloudmon Collector Installation"

    if collector_is_installed; then
        log_info "Existing collector installation detected at ${CLOUDMON_COLLECTOR_DIR}."
        if verify_collector_health; then
            log_success "Existing collector installation looks healthy. Skipping installation."
            summary_add "Collector" "SKIPPED" "Existing installation detected"
        else
            log_warn "Existing collector installation detected but appears unhealthy."
            if ! ask_yn "Continue without modifying the existing installation?" "no"; then
                log_error "Aborting. Use '--uninstall --collector' to remove it first, then re-run install."
                summary_add "Collector" "FAILED" "Unhealthy existing installation"
                exit 1
            fi
            summary_add "Collector" "WARN" "Existing installation unhealthy — left untouched"
        fi
    else
        [[ -z "${CONTROLLER_HOST}" ]] && prompt_user "Controller host/IP" "CONTROLLER_HOST"
        [[ -z "${API_KEY}" ]]         && prompt_user "API key (Settings → API Keys)" "API_KEY" "true"
        ensure_package_url || return 1

        install_collector_deps || return 1

        local installer="${TMP_DIR}/cloudmon-collector.run"
        log_info "Downloading collector package..."
        curl -fsSL "${URL_COLLECTOR}" -o "${installer}" \
            || { log_error "Collector download failed."; summary_add "Collector" "FAILED" "Download failed"; confirm_continue "Collector download"; return 1; }

        chmod +x "${installer}"
        log_info "Running collector installer with --server ${CONTROLLER_HOST} --key ***..."

        "${installer}" \
            --server "${CONTROLLER_HOST}" \
            --key    "${API_KEY}" >> "${LOG_FILE}" 2>&1 \
            || { log_error "Collector installer failed."; summary_add "Collector" "FAILED" "Installer failed"; confirm_continue "Collector install"; return 1; }

        log_info "Reloading systemd units..."
        systemctl daemon-reload >> "${LOG_FILE}" 2>&1
        sleep 5

        local col_running
        col_running="$(systemctl list-units --type=service --state=running 2>/dev/null \
                       | grep -c 'cloudmon-collector@' || true)"

        if [[ "${col_running}" -gt 0 ]]; then
            log_success "Collector running (${col_running} units active)."
            summary_add "Collector" "SUCCESS" "${col_running} cloudmon-collector@ units running"
        else
            log_warn "Collector units not running. Check: systemctl list-units | grep cloudmon-collector"
            summary_add "Collector" "WARN" "Verify manually"
        fi
    fi

    # Probe is always checked/installed alongside the collector (idempotent)
    install_probe || true
}

uninstall_collector() {
    log_step "Cloudmon Collector Removal"

    local has_units=false
    systemctl list-units --type=service --all 2>/dev/null | grep -q 'cloudmon-collector' && has_units=true

    if [[ "${has_units}" == false && ! -d "${CLOUDMON_COLLECTOR_DIR}" ]]; then
        log_info "Collector is not installed. Nothing to do."
        summary_add "Collector" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "the Cloudmon Collector"; then
        summary_add "Collector" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    log_info "Stopping and disabling collector services..."
    systemctl stop    'cloudmon-collector@*' >> "${LOG_FILE}" 2>&1 || true
    systemctl disable 'cloudmon-collector@*' >> "${LOG_FILE}" 2>&1 || true

    if [[ -d "${CLOUDMON_COLLECTOR_DIR}" ]]; then
        log_info "Removing collector installation..."
        rm -rf "${CLOUDMON_COLLECTOR_DIR}"
    fi

    rm -f /etc/systemd/system/cloudmon-collector@.service
    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    log_success "Collector removed."
    summary_add "Collector" "SUCCESS" "Removed"
}

# =============================================================================
# PROBE
# install_probe()   — idempotent. Detects an existing installation and skips
#                      it if the service is running. Unlike the Collector, the
#                      Probe installer takes no controller host or API key —
#                      it is downloaded and run directly.
# uninstall_probe() — the only function that stops the service and removes
#                      the probe installation and systemd unit.
# =============================================================================

probe_is_installed() { [[ -d "${CLOUDMON_PROBE_DIR}" ]]; }

verify_probe_health() {
    systemctl is-active cloudmon-probe &>/dev/null
}

install_probe() {
    log_step "Cloudmon Probe Installation"

    if probe_is_installed; then
        log_info "Existing probe installation detected at ${CLOUDMON_PROBE_DIR}."
        if verify_probe_health; then
            log_success "Existing probe installation looks healthy. Skipping installation."
            summary_add "Probe" "SKIPPED" "Existing installation detected"
        else
            log_warn "Existing probe installation detected but is not running."
            summary_add "Probe" "WARN" "Existing installation present but not running — left untouched"
        fi
        return 0
    fi

    # The Probe requires no controller host or API key — it is run directly.
    ensure_package_url || return 1

    local installer="${TMP_DIR}/cloudmon-probe.run"
    log_info "Downloading probe package..."
    curl -fsSL "${URL_PROBE}" -o "${installer}" \
        || { log_error "Probe download failed."; summary_add "Probe" "FAILED" "Download failed"; confirm_continue "Probe download"; return 1; }

    chmod +x "${installer}"
    log_info "Running probe installer..."

    local probe_exit=0
    "${installer}" >> "${LOG_FILE}" 2>&1 || probe_exit=$?

    if [[ "${probe_exit}" -ne 0 ]]; then
        log_warn "Probe installer exited with code ${probe_exit}."
        log_warn "Check: tail -20 /var/log/cloudmon-probe-install.log"
        log_warn "Debug: journalctl -u cloudmon-probe -n 30 --no-pager"
        summary_add "Probe" "WARN" "Installer failed (exit ${probe_exit}) — check logs"
        return 0
    fi

    systemctl daemon-reload >> "${LOG_FILE}" 2>&1
    sleep 5

    if systemctl is-active cloudmon-probe &>/dev/null; then
        log_success "Probe installed and running."
        summary_add "Probe" "SUCCESS" "Service: cloudmon-probe"
    else
        log_warn "Probe service not running."
        log_warn "Debug: journalctl -u cloudmon-probe -n 30 --no-pager"
        summary_add "Probe" "WARN" "Not running — check logs"
    fi
}

uninstall_probe() {
    log_step "Cloudmon Probe Removal"

    local has_unit=false
    systemctl list-units --type=service --all 2>/dev/null | grep -q 'cloudmon-probe' && has_unit=true

    if [[ "${has_unit}" == false && ! -d "${CLOUDMON_PROBE_DIR}" ]]; then
        log_info "Probe is not installed. Nothing to do."
        summary_add "Probe" "SKIPPED" "Not installed"
        return 0
    fi

    if ! confirm_destructive "the Cloudmon Probe"; then
        summary_add "Probe" "SKIPPED" "Uninstall cancelled by user"
        return 0
    fi

    log_info "Stopping and disabling probe service..."
    systemctl stop    cloudmon-probe >> "${LOG_FILE}" 2>&1 || true
    systemctl disable cloudmon-probe >> "${LOG_FILE}" 2>&1 || true

    if [[ -d "${CLOUDMON_PROBE_DIR}" ]]; then
        log_info "Removing probe installation..."
        rm -rf "${CLOUDMON_PROBE_DIR}"
    fi

    rm -f /etc/systemd/system/cloudmon-probe.service
    systemctl daemon-reload >> "${LOG_FILE}" 2>&1 || true

    log_success "Probe removed."
    summary_add "Probe" "SUCCESS" "Removed"
}

# =============================================================================
# USAGE
# =============================================================================

_usage() {
    echo -e "
${BOLD}Usage:${NC}
  sudo bash cloudmon-install.sh --install|--uninstall [COMPONENT] [OPTIONS]

${BOLD}Mode (required — choose exactly one):${NC}
  --install         Install the selected component(s). NEVER removes, purges,
                     or modifies an existing installation — only verifies it
                     and skips if healthy.
  --uninstall        Uninstall the selected component(s). This is the ONLY
                     mode that stops services, purges packages, and deletes
                     data, configuration, and directories.

${BOLD}Components (choose at least one):${NC}
  --controller     Node.js 24+ + Nginx (1.${NGINX_MINOR_REQUIRED}+) + Redis + Controller + .env + nginx configuration
                   (--install prompts for Database IP/Hostname, Username, Password, and Package URL —
                    skipped automatically when combined with --mongodb/--all, which points it at the local MongoDB)
                   (Redis is installed automatically as part of the Controller — use --redis on its own
                    if you only want a standalone Redis install/uninstall)
  --collector      libpcap-dev + nmap + Collector + Probe
                   (--install prompts for Controller IP/hostname, API key, and Package URL)
                   (the Probe requires no credentials and is run directly)
  --mongodb        MongoDB (local), including mongosh
  --redis          Redis server, standalone (independent of --controller)
  --mongosh        MongoDB Shell (mongosh) only, standalone — does not
                   install/uninstall the mongodb-org server itself
  --all            All of the above

${BOLD}Options:${NC}
  --controller-host <ip>    Controller IP/hostname (required for --collector)
  --api-key <key>           API key from Cloudmon UI (required for --collector)
  --db-host <ip/hostname>   Database IP/Hostname (for --controller)
  --db-user <user>          Database username (for --controller)
  --db-pass <pass>          Database password (for --controller)
  --package-url <url>       Base package URL used to download components and
                             written to controller .env (for --controller)
  --log-dir <path>          Log directory (default: ${LOG_DIR_DEFAULT})
  --non-interactive         Skip all prompts (also skips uninstall confirmations)
  -h, --help

${BOLD}Uninstall examples:${NC}
  sudo bash cloudmon-install.sh --uninstall --controller
  sudo bash cloudmon-install.sh --uninstall --collector
  sudo bash cloudmon-install.sh --uninstall --mongodb
  sudo bash cloudmon-install.sh --uninstall --redis
  sudo bash cloudmon-install.sh --uninstall --mongosh
  sudo bash cloudmon-install.sh --uninstall --all

${BOLD}Note:${NC}  --install never touches an existing installation; it only checks
       and skips. All destructive operations live exclusively behind --uninstall.

${BOLD}Supported:${NC}  Ubuntu 24.04 (noble) only
"
}

# =============================================================================
# ARGUMENT PARSING
# =============================================================================

# Shown on invalid-usage errors: a single line on how to invoke the script,
# without dumping the full help text. Use '-h/--help' for the full reference.
_usage_line() {
    echo -e "${BOLD}Usage:${NC} sudo bash cloudmon-install.sh --install|--uninstall --controller|--collector|--mongodb|--redis|--mongosh|--all [OPTIONS]"
    echo -e "${DIM}       Run with -h or --help for the full option reference.${NC}"
}

# _usage_error <message> — prints a specific, friendly reason followed by the
# usage line, marks this as a usage error (so cleanup() stays quiet), and
# exits. This replaces raw bash errors (e.g. "unbound variable") with a
# helpful prompt for any malformed invocation.
_usage_error() {
    local message="$1"
    USAGE_ERROR=true
    echo -e "  ${RED}${message}${NC}"
    echo ""
    _usage_line
    exit 1
}

# _require_value <flag> <remaining_args...> — shows a friendly usage error and
# exits if a flag that requires a value wasn't given one (instead of letting
# `set -u` throw a raw "unbound variable" error on "$2"). Must be called
# directly (not inside a subshell/command-substitution), since it exits.
_require_value() {
    local flag="$1"; shift
    if [[ $# -lt 2 ]]; then
        _usage_error "Missing value for ${flag}."
    fi
}

parse_args() {
    if [[ $# -eq 0 ]]; then _usage; exit 0; fi
    local got_component=false
    local mode_count=0
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --install)         MODE="install";   mode_count=$((mode_count + 1)); shift ;;
            --uninstall)       MODE="uninstall"; mode_count=$((mode_count + 1)); shift ;;
            --controller)      DO_CONTROLLER=true; got_component=true; shift ;;
            --collector)       DO_COLLECTOR=true;  got_component=true; shift ;;
            --mongodb)         DO_MONGODB=true;    got_component=true; shift ;;
            --redis)           DO_REDIS=true;      got_component=true; shift ;;
            --mongosh)         DO_MONGOSH=true;    got_component=true; shift ;;
            --all)             DO_CONTROLLER=true; DO_COLLECTOR=true; DO_MONGODB=true; DO_REDIS=true; DO_MONGOSH=true; got_component=true; shift ;;
            --controller-host) _require_value "$1" "$@"; CONTROLLER_HOST="$2"; shift 2 ;;
            --api-key)         _require_value "$1" "$@"; API_KEY="$2"; shift 2 ;;
            --db-host)         _require_value "$1" "$@"; DB_HOST="$2"; shift 2 ;;
            --db-user)         _require_value "$1" "$@"; DB_USER="$2"; shift 2 ;;
            --db-pass)         _require_value "$1" "$@"; DB_PASS="$2"; shift 2 ;;
            --package-url)     _require_value "$1" "$@"; PACKAGE_URL="$2"; shift 2 ;;
            --log-dir)         _require_value "$1" "$@"; LOG_DIR="$2"; shift 2 ;;
            --non-interactive) NON_INTERACTIVE=true; shift ;;
            -h|--help)         _usage; exit 0 ;;
            *) _usage_error "Unknown or misplaced option: '$1'" ;;
        esac
    done

    if [[ "${mode_count}" -eq 0 ]]; then
        _usage_error "You must specify a mode: --install or --uninstall."
    fi

    if [[ "${mode_count}" -gt 1 ]]; then
        _usage_error "Specify only one mode: --install or --uninstall, not both."
    fi

    if [[ "${got_component}" == false ]]; then
        _usage_error "You must specify at least one component: --controller, --collector, --mongodb, --redis, --mongosh, or --all."
    fi
}

# =============================================================================
# PRINT SUMMARY
# =============================================================================

print_summary() {
    echo ""
    echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}"
    if [[ "${MODE}" == "install" ]]; then
        echo -e "${BOLD}${CYAN}║            INSTALLATION SUMMARY               ║${NC}"
    else
        echo -e "${BOLD}${CYAN}║           UNINSTALLATION SUMMARY              ║${NC}"
    fi
    echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}"
    local i
    for (( i=0; i<${#SUMMARY_COMPONENT[@]}; i++ )); do
        local color="${GREEN}"
        [[ "${SUMMARY_STATUS[$i]}" == "FAILED"  ]] && color="${RED}"
        [[ "${SUMMARY_STATUS[$i]}" == "WARN"    ]] && color="${YELLOW}"
        [[ "${SUMMARY_STATUS[$i]}" == "SKIPPED" ]] && color="${DIM}"
        echo -e " ${color}▸${NC} ${SUMMARY_COMPONENT[$i]} : ${color}${SUMMARY_STATUS[$i]}${NC} : ${SUMMARY_DETAIL[$i]}"
    done
    echo ""

    if [[ "${MODE}" == "install" && "${DO_CONTROLLER}" == true ]]; then
        local server_ip
        server_ip="$(hostname -I | awk '{print $1}')"
        echo -e "${BOLD}${GREEN}  Controller ready!${NC}"
        echo -e "  URL      : ${CYAN}http://${server_ip}${NC}"
        echo -e "  Login    : ${YELLOW}Use the pre-registration login.${NC}"
        echo -e "  API Key  : Settings → API Keys → Generate"
        echo ""
        echo -e "  Next — install collector:"
        echo -e "  ${DIM}sudo bash cloudmon-install.sh --install --collector --controller-host ${server_ip} --api-key <key>${NC}"
        echo ""
    fi

    if [[ "${MODE}" == "uninstall" ]]; then
        echo -e "${BOLD}${GREEN}  Uninstallation complete.${NC}"
        echo ""
    fi

    echo -e "${DIM}Log: ${LOG_FILE}${NC}"
}

# =============================================================================
# BANNER
# =============================================================================

print_banner() {
    [[ -t 1 ]] && clear
    echo ""
    echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BOLD}${CYAN}║        Cloudmon Production Installer v${SCRIPT_VERSION}              ║${NC}"
    echo -e "${BOLD}${CYAN}║                  Ubuntu 24.04 Required                       ║${NC}"
    if [[ "${MODE}" == "install" ]]; then
        echo -e "${BOLD}${CYAN}║   Mode: INSTALL — existing installations are never touched   ║${NC}"
    else
        echo -e "${BOLD}${CYAN}║   Mode: UNINSTALL — this will remove services, data, config  ║${NC}"
    fi
    echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
    echo ""
}

# =============================================================================
# MAIN
# =============================================================================

main() {
    _init_log
    parse_args "$@"
    print_banner
    log_info "Log file: ${LOG_FILE}"
    log_info "Mode: ${MODE}"

    check_root
    check_os

    if [[ "${MODE}" == "install" ]]; then
        install_jq
        check_dependencies || true

        # MongoDB: standalone local install for --mongodb / --all only.
        # --controller does NOT install MongoDB — it connects to a user-supplied database.
        if [[ "${DO_MONGODB}" == true ]]; then
            install_mongodb || true
        fi

        # Redis: standalone install for --redis / --all. --controller installs
        # its own Redis internally regardless of this flag (see install_controller).
        if [[ "${DO_REDIS}" == true ]]; then
            install_redis || true
        fi

        # mongosh: standalone client-only install for --mongosh / --all.
        # If --mongodb is also selected, mongosh is already installed as part
        # of install_mongodb(), so this will simply detect and skip it.
        if [[ "${DO_MONGOSH}" == true ]]; then
            install_mongosh || true
        fi

        # Controller: Database inputs + Node.js 24+ + Nginx (1.25+) + Redis + Controller .run + .env + nginx configuration
        if [[ "${DO_CONTROLLER}" == true ]]; then
            install_controller || true
        fi

        # Collector + Probe: prompts for host/key internally if not passed via flags
        if [[ "${DO_COLLECTOR}" == true ]]; then
            install_collector || true  # installs/verifies probe internally
        fi
    else
        # Uninstall order: collector/probe and controller first, then the
        # standalone Redis/mongosh flags, MongoDB last, since the controller
        # depends on a database being reachable.
        if [[ "${DO_COLLECTOR}" == true ]]; then
            uninstall_collector || true
            uninstall_probe || true
        fi

        if [[ "${DO_CONTROLLER}" == true ]]; then
            uninstall_controller || true
        fi

        # Redis: standalone uninstall for --redis / --all. If --controller was
        # also passed, uninstall_controller() already removed Redis above;
        # uninstall_redis() is idempotent and will just detect it's gone.
        if [[ "${DO_REDIS}" == true ]]; then
            uninstall_redis || true
        fi

        # mongosh: standalone uninstall for --mongosh / --all. Does not affect
        # the mongodb-org server package — that's handled by --mongodb below.
        if [[ "${DO_MONGOSH}" == true ]]; then
            uninstall_mongosh || true
        fi

        if [[ "${DO_MONGODB}" == true ]]; then
            uninstall_mongodb || true
        fi
    fi

    print_summary
}

main "$@"
