#!/usr/bin/env bash
#===============================================================================
# Cortex Installer — All-in-one setup for target Jetson devices
#
# Downloads the latest Cortex build from S3 (via license key) and sets up
# everything needed to run the surgical video platform on a fresh Jetson Thor.
#
# What it does:
#   1. Installs runtime dependencies (apt packages)
#   2. Redeems license key → downloads build artifacts via pre-signed S3 URLs
#   3. Extracts binaries/libs to /opt/verus/
#   4. Extracts SDK libs (Holoscan, CUDA, TensorRT)
#   5. Configures dynamic linker (ldconfig)
#   6. Configures NvSciIpc endpoints, permissions, directories
#   7. Installs systemd services
#   8. Sets up IoT Core (X.509 certificate for cloud connectivity)
#   9. Installs remote desktop (x11vnc + websockify)
#
# Usage:
#   sudo ./install_cortex.sh --key VTXS-XXXX-XXXX-XXXX    # Fresh install
#   sudo ./install_cortex.sh --update --key VTXS-...       # Update app only
#   sudo ./install_cortex.sh --status                      # Show install status
#   sudo ./install_cortex.sh --uninstall                   # Remove everything
#
# Prerequisites:
#   - Jetson Thor with JetPack 7
#   - License key from the Verus portal (my.verussurgical.com)
#   - Internet connectivity, curl, jq
#
#===============================================================================

set -euo pipefail

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

# Configuration
S3_BUCKET="verus-cortex-releases"
AWS_PROFILE="cortex-device-user"
AWS_REGION="eu-central-1"
ARCH="aarch64"
S3_PREFIX="s3://${S3_BUCKET}/latest"
API_URL="https://euxkl75icd.execute-api.eu-central-1.amazonaws.com/prod"
DEVICE_CONF_DIR="/opt/verus/etc"
DEVICE_CONF="${DEVICE_CONF_DIR}/device.conf"

INSTALL_PREFIX="/opt/verus"
HOST_HOLOSCAN="/opt/nvidia/holoscan"
HOST_CUDA_LIBS="${INSTALL_PREFIX}/lib/cuda"
HOST_TENSORRT_LIBS="${INSTALL_PREFIX}/lib/tensorrt"
SYSTEMD_DIR="/etc/systemd/system"
LD_CONF_FILE="/etc/ld.so.conf.d/cortex.conf"
NVSCIIPC_CFG="/etc/nvsciipc.cfg"

DOWNLOAD_DIR="/tmp/cortex-install"

# Tarball names (must match publish_release.sh)
APP_TARBALL="cortex-latest-${ARCH}.tar.gz"
SDK_TARBALL="cortex-sdk-latest-${ARCH}.tar.gz"
MODELS_TARBALL="cortex-models-latest-${ARCH}.tar.gz"

# Logging
log()      { echo -e "${BLUE}[INSTALL]${NC} $*"; }
log_ok()   { echo -e "${GREEN}[INSTALL] ✓${NC} $*"; }
log_warn() { echo -e "${YELLOW}[INSTALL] !${NC} $*"; }
log_err()  { echo -e "${RED}[INSTALL] ✗${NC} $*" >&2; }
log_step() { echo -e "\n${CYAN}[INSTALL]${NC} ${BOLD}$*${NC}"; }

#-------------------------------------------------------------------------------
# Parse arguments
#-------------------------------------------------------------------------------
MODE="full"        # full, update, status, uninstall
INSTALL_KEY=""
SKIP_DEPS=false
SKIP_SDK=false
SKIP_MODELS=false
BUILD_ENGINES=false
NEEDS_REBOOT=false
DEPLOY_USER="${SUDO_USER:-$(whoami)}"
DEPLOY_GROUP="$(id -gn "$DEPLOY_USER")"

while [[ $# -gt 0 ]]; do
    case "$1" in
        --key)            INSTALL_KEY="$2"; shift 2 ;;
        --update)         MODE=update; SKIP_DEPS=true; SKIP_SDK=true; SKIP_MODELS=true; shift ;;
        --update-full)    MODE=update; shift ;;
        --status)         MODE=status; shift ;;
        --uninstall)      MODE=uninstall; shift ;;
        --skip-deps)      SKIP_DEPS=true; shift ;;
        --skip-sdk)       SKIP_SDK=true; shift ;;
        --skip-models)    SKIP_MODELS=true; shift ;;
        --skip-services)  log_warn "--skip-services is deprecated (services are always installed)"; shift ;;
        --build-engines)  BUILD_ENGINES=true; shift ;;
        --user)           DEPLOY_USER="$2"; DEPLOY_GROUP="$(id -gn "$2")"; shift 2 ;;
        -h|--help)
            cat << 'EOF'
Cortex Installer — All-in-one setup for target devices

Usage: sudo ./scripts/install_cortex.sh --key VTXS-XXXX-XXXX-XXXX [OPTIONS]

Modes:
    --key KEY         Install using a license key (no AWS CLI needed)
    --update --key    Quick update: app binaries only (skips deps, SDK, models)
    --update-full     Full update: re-downloads and re-installs everything
    --status          Show current installation status
    --uninstall       Remove all Cortex artifacts and services

Options:
    --skip-deps       Skip apt dependency installation
    --skip-sdk        Skip SDK libs download (Holoscan/CUDA/TensorRT/FFmpeg)
    --skip-models     Skip AI model download
    --build-engines   Build TensorRT engines after install (slow, ~5-10min)
    --user USER       Set the runtime user (default: $SUDO_USER or current user)
    -h, --help        Show this help

Examples:
    First-time setup with license key:
        sudo ./install_cortex.sh --key VTXS-A3B7-K9M2-X4P1

    Quick update (app binaries only):
        sudo ./install_cortex.sh --update --key VTXS-A3B7-K9M2-X4P1

    Full update (deps + SDK + models + app):
        sudo ./install_cortex.sh --update-full --key VTXS-A3B7-K9M2-X4P1

After install:
    /opt/verus/bin/run_multiprocess.sh
EOF
            exit 0
            ;;
        *) log_err "Unknown option: $1"; exit 1 ;;
    esac
done

#-------------------------------------------------------------------------------
# Status
#-------------------------------------------------------------------------------
if [[ "$MODE" == "status" ]]; then
    echo -e "${BOLD}Cortex Installation Status${NC}"
    echo "─────────────────────────────────────────"

    # Installed version
    if [[ -f "${INSTALL_PREFIX}/manifest.json" ]]; then
        echo -e "Manifest:        ${GREEN}found${NC}"
        cat "${INSTALL_PREFIX}/manifest.json" | while IFS= read -r line; do
            echo "  $line"
        done
    else
        echo -e "Manifest:        ${YELLOW}not found${NC}"
    fi

    # Binaries
    if [[ -d "${INSTALL_PREFIX}/bin" ]]; then
        bins=$(ls "${INSTALL_PREFIX}/bin/"cortex* 2>/dev/null | wc -l)
        echo -e "Install prefix:  ${GREEN}${INSTALL_PREFIX}${NC} (${bins} binaries)"
        for bin in "${INSTALL_PREFIX}/bin/"cortex*; do
            [[ -x "$bin" ]] && echo "  $(basename "$bin")"
        done
    else
        echo -e "Install prefix:  ${RED}not installed${NC}"
    fi

    # Libraries
    if [[ -d "${INSTALL_PREFIX}/lib" ]]; then
        libs=$(find "${INSTALL_PREFIX}/lib" -name "*.so*" -type f 2>/dev/null | wc -l)
        echo -e "Verus libs:      ${GREEN}${libs} .so files${NC}"
    fi

    # SDK libs
    for label_path in "Holoscan:${HOST_HOLOSCAN}/lib" "CUDA:${HOST_CUDA_LIBS}" "TensorRT:${HOST_TENSORRT_LIBS}"; do
        label="${label_path%%:*}"
        path="${label_path#*:}"
        if [[ -d "$path" ]]; then
            count=$(find "$path" -name "*.so*" -type f 2>/dev/null | wc -l)
            echo -e "  ${label}:$(printf '%*s' $((12 - ${#label})) '')${GREEN}${count} libs${NC}"
        else
            echo -e "  ${label}:$(printf '%*s' $((12 - ${#label})) '')${YELLOW}not installed${NC}"
        fi
    done

    # TensorRT engines
    echo "AI models:"
    for model_dir in "${INSTALL_PREFIX}/models/oob" "${INSTALL_PREFIX}/models/tool_segmentation"; do
        name=$(basename "$model_dir")
        if [[ -d "$model_dir" ]]; then
            has_onnx=$(find "$model_dir" -name "*.onnx" 2>/dev/null | wc -l)
            has_engine=$(find "$model_dir" -name "*.engine" 2>/dev/null | wc -l)
            echo -e "  ${name}: ${GREEN}${has_onnx} onnx, ${has_engine} engine${NC}"
        else
            echo -e "  ${name}: ${YELLOW}not found${NC}"
        fi
    done

    # LD config
    if [[ -f "$LD_CONF_FILE" ]]; then
        echo -e "LD config:       ${GREEN}${LD_CONF_FILE}${NC}"
    else
        echo -e "LD config:       ${YELLOW}not installed${NC}"
    fi

    # NvSciIpc
    if [[ -f "$NVSCIIPC_CFG" ]] && grep -q "nvscistream_102" "$NVSCIIPC_CFG" 2>/dev/null; then
        echo -e "NvSciIpc:        ${GREEN}endpoints configured${NC}"
    else
        echo -e "NvSciIpc:        ${YELLOW}not configured${NC}"
    fi

    # systemd services
    echo "systemd services:"
    for svc in cortex-casemgmt cortex-uploader x11vnc websockify; do
        if [[ -f "${SYSTEMD_DIR}/${svc}.service" ]]; then
            status=$(systemctl is-active "$svc" 2>/dev/null || echo "inactive")
            enabled=$(systemctl is-enabled "$svc" 2>/dev/null || echo "disabled")
            echo -e "  ${svc}: ${GREEN}installed${NC} (${status}, ${enabled})"
        else
            echo -e "  ${svc}: ${YELLOW}not installed${NC}"
        fi
    done

    # IoT Core
    IOT_CERT_DIR="${INSTALL_PREFIX}/certs"
    if [[ -f "${IOT_CERT_DIR}/device.pem.crt" ]]; then
        IOT_THING=""
        for conf in "${DEVICE_CONF}" "/home/${SUDO_USER:-$(whoami)}/.config/verus/device.conf"; do
            [[ -f "$conf" ]] && IOT_THING=$(grep '^iot_thing_name=' "$conf" 2>/dev/null | cut -d= -f2) && break
        done
        echo -e "IoT Core:        ${GREEN}certificate present${NC} (thing: ${IOT_THING:-unknown})"
        # Check cert expiry
        if command -v openssl &>/dev/null; then
            CERT_EXPIRY=$(openssl x509 -enddate -noout -in "${IOT_CERT_DIR}/device.pem.crt" 2>/dev/null | cut -d= -f2)
            [[ -n "$CERT_EXPIRY" ]] && echo -e "  Cert expires:  ${CERT_EXPIRY}"
        fi
    else
        echo -e "IoT Core:        ${YELLOW}not provisioned${NC}"
    fi

    # Device config
    if [[ -f "${DEVICE_CONF}" ]]; then
        DEVICE_ID=$(grep '^device_id=' "${DEVICE_CONF}" 2>/dev/null | cut -d= -f2)
        TENANT_ID=$(grep '^tenant_id=' "${DEVICE_CONF}" 2>/dev/null | cut -d= -f2)
        echo -e "Device ID:       ${GREEN}${DEVICE_ID:-not set}${NC}"
        echo -e "Tenant:          ${GREEN}${TENANT_ID:-not set}${NC}"
    else
        echo -e "Device config:   ${YELLOW}not found${NC}"
    fi

    exit 0
fi

#-------------------------------------------------------------------------------
# Uninstall
#-------------------------------------------------------------------------------
if [[ "$MODE" == "uninstall" ]]; then
    log_step "Uninstalling Cortex"

    # Stop and remove all services
    for svc in cortex-casemgmt cortex-uploader cortex-ui cortex-video-core x11vnc websockify vnc-rotate.timer; do
        if systemctl is-active "$svc" &>/dev/null; then
            log "Stopping ${svc}..."
            systemctl stop "$svc" || true
        fi
        if [[ -f "${SYSTEMD_DIR}/${svc}.service" ]] || [[ -f "${SYSTEMD_DIR}/${svc}" ]]; then
            systemctl disable "$svc" 2>/dev/null || true
            rm -f "${SYSTEMD_DIR}/${svc}.service" "${SYSTEMD_DIR}/${svc}"
        fi
    done
    systemctl daemon-reload 2>/dev/null || true
    log_ok "Services stopped and removed"

    # Kill running cortex processes
    pkill -f "bin/cortex_" 2>/dev/null || true

    # Remove install prefix (/opt/verus — includes binaries, libs, certs, config)
    [[ -d "$INSTALL_PREFIX" ]] && rm -rf "$INSTALL_PREFIX" && log_ok "Removed ${INSTALL_PREFIX}"

    # Remove user-writable config
    REAL_HOME=$(eval echo "~${DEPLOY_USER}")
    rm -rf "${REAL_HOME}/.config/verus" 2>/dev/null && log_ok "Removed user config"

    # Remove Holoscan libs (only if we deployed them)
    if [[ -d "${HOST_HOLOSCAN}/lib" ]]; then
        rm -rf "${HOST_HOLOSCAN}/lib"
        rmdir "${HOST_HOLOSCAN}" 2>/dev/null || true
        rmdir "$(dirname "${HOST_HOLOSCAN}")" 2>/dev/null || true
        log_ok "Removed Holoscan libs"
    fi

    # Remove LD config
    if [[ -f "$LD_CONF_FILE" ]]; then
        rm -f "$LD_CONF_FILE"
        ldconfig
        log_ok "Removed ${LD_CONF_FILE}"
    fi

    # Clean up sockets
    rm -f /tmp/cortex-*.sock 2>/dev/null || true

    log_ok "Uninstall complete"
    echo ""
    echo -e "  ${YELLOW}Note: NvSciIpc config in ${NVSCIIPC_CFG} was not modified.${NC}"
    echo -e "  ${YELLOW}Note: IoT Thing and certificate in AWS were not deleted.${NC}"
    echo -e "  ${YELLOW}      Delete them in the AWS Console if decommissioning this device.${NC}"
    exit 0
fi

#-------------------------------------------------------------------------------
# Pre-flight checks
#-------------------------------------------------------------------------------
log_step "Pre-flight checks"

if [[ $EUID -ne 0 ]]; then
    log_err "This script must be run with sudo"
    exit 1
fi

# Download mode: --key (license key via API) or --update (IoT/AWS CLI)
# --update + --key is valid: re-uses the license key for pre-signed URLs (recommended)
USE_LICENSE_KEY=false
if [[ -n "$INSTALL_KEY" ]]; then
    USE_LICENSE_KEY=true
    # Validate key format (charset matches API: no 0/O/1/I)
    if [[ ! "$INSTALL_KEY" =~ ^VTXS-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$ ]]; then
        log_err "Invalid key format. Expected: VTXS-XXXX-XXXX-XXXX (uppercase, no 0/O/1/I)"
        exit 1
    fi
    # Install curl and jq if missing (needed for license key flow)
    MISSING_TOOLS=()
    command -v curl &>/dev/null || MISSING_TOOLS+=(curl)
    command -v jq &>/dev/null || MISSING_TOOLS+=(jq)
    if [[ ${#MISSING_TOOLS[@]} -gt 0 ]]; then
        log "Installing ${MISSING_TOOLS[*]}..."
        apt-get update -qq && apt-get install -y --no-install-recommends "${MISSING_TOOLS[@]}" || {
            log_err "Failed to install ${MISSING_TOOLS[*]}. Install manually: sudo apt-get install ${MISSING_TOOLS[*]}"
            exit 1
        }
    fi
    log_ok "License key mode (curl + jq available)"
elif [[ "$MODE" == "update" ]]; then
    # Update mode: download latest build using IoT credentials or IAM profile
    # Check for IoT cert (preferred — no IAM user needed)
    UPDATE_IOT_CERT=""
    UPDATE_IOT_KEY=""
    UPDATE_IOT_CA=""
    UPDATE_IOT_CRED_ENDPOINT=""
    UPDATE_IOT_ROLE_ALIAS=""
    for conf in "${DEVICE_CONF}" "/home/${DEPLOY_USER}/.config/verus/device.conf"; do
        [[ -f "$conf" ]] || continue
        UPDATE_IOT_CERT=$(grep '^iot_cert_path=' "$conf" 2>/dev/null | cut -d= -f2)
        UPDATE_IOT_KEY=$(grep '^iot_key_path=' "$conf" 2>/dev/null | cut -d= -f2)
        UPDATE_IOT_CA=$(grep '^iot_ca_path=' "$conf" 2>/dev/null | cut -d= -f2)
        UPDATE_IOT_CRED_ENDPOINT=$(grep '^iot_credential_endpoint=' "$conf" 2>/dev/null | cut -d= -f2)
        UPDATE_IOT_ROLE_ALIAS=$(grep '^iot_role_alias=' "$conf" 2>/dev/null | cut -d= -f2)
        [[ -n "$UPDATE_IOT_CERT" ]] && break
    done

    if [[ -n "$UPDATE_IOT_CERT" ]] && [[ -f "$UPDATE_IOT_CERT" ]] && [[ -n "$UPDATE_IOT_CRED_ENDPOINT" ]]; then
        # Fetch temp credentials via IoT Credential Provider
        for cmd in curl jq; do
            if ! command -v "$cmd" &>/dev/null; then
                log_err "$cmd is required for IoT credential fetch"
                exit 1
            fi
        done
        IOT_CREDS=$(curl -s --cert "$UPDATE_IOT_CERT" --key "$UPDATE_IOT_KEY" --cacert "$UPDATE_IOT_CA" \
            "https://${UPDATE_IOT_CRED_ENDPOINT}/role-aliases/${UPDATE_IOT_ROLE_ALIAS}/credentials" 2>/dev/null)
        export AWS_ACCESS_KEY_ID=$(echo "$IOT_CREDS" | jq -r '.credentials.accessKeyId // empty')
        export AWS_SECRET_ACCESS_KEY=$(echo "$IOT_CREDS" | jq -r '.credentials.secretAccessKey // empty')
        export AWS_SESSION_TOKEN=$(echo "$IOT_CREDS" | jq -r '.credentials.sessionToken // empty')
        if [[ -n "$AWS_ACCESS_KEY_ID" ]]; then
            AWS_PROFILE=""  # clear profile so aws CLI uses env vars
            log_ok "IoT credentials valid (update via credential provider)"
        else
            log_err "Failed to get IoT credentials for update."
            echo "  Re-run with: --update --key VTXS-XXXX-XXXX-XXXX"
            exit 1
        fi
    else
        log_err "No IoT certificate found on this device."
        echo "  Re-run with: --update --key VTXS-XXXX-XXXX-XXXX"
        exit 1
    fi
else
    log_err "No license key provided."
    echo "  Fresh install:  sudo ./scripts/install_cortex.sh --key VTXS-XXXX-XXXX-XXXX"
    echo "  Update:         sudo ./scripts/install_cortex.sh --update --key VTXS-XXXX-XXXX-XXXX"
    echo "  Get a key from your admin on the Verus portal."
    exit 1
fi

#-------------------------------------------------------------------------------
# Step 1: Install runtime dependencies
#-------------------------------------------------------------------------------
if ! $SKIP_DEPS; then
    log_step "Step 1/9: Installing runtime dependencies"

    RUNTIME_PKGS=(
        # Tools (needed by installer and runtime scripts)
        curl jq
        # gRPC + Protobuf runtime (all services communicate via gRPC)
        libgrpc++1.51t64 libprotobuf32t64
        # NvSci IPC (NvSciStream — may be missing on minimal JetPack installs)
        nvidia-l4t-nvsci
        # FFmpeg runtime deps (FFmpeg 7.x libs are bundled in the app tarball)
        libx264-164 libx265-199 libmp3lame0 libopus0 libvpx9
        # Image
        libjpeg-turbo8
        # Database
        libsqlite3-0
        # TLS
        libssl3t64
        # Qt6 runtime
        libqt6core6 libqt6gui6 libqt6quick6 libqt6qml6
        libqt6quickcontrols2-6 libqt6opengl6
        qml6-module-qtquick qml6-module-qtquick-controls
        qml6-module-qtquick-layouts qml6-module-qtquick-window
        qml6-module-qtqml-workerscript
        qml6-module-qtquick-templates
        # Qt6 WebEngine runtime for teleconferencing
        libqt6webenginequick6
        qml6-module-qtwebengine
        # Qt6 Multimedia runtime for video playback
        qml6-module-qtmultimedia
        # v4l2loopback for teleconferencing virtual webcam
        v4l2loopback-dkms v4l2loopback-utils
    )

    apt-get update -qq
    apt-get install -y --no-install-recommends "${RUNTIME_PKGS[@]}" || {
        log_warn "Some packages may not be available; continuing"
    }
    log_ok "Runtime dependencies installed"

    # v4l2loopback: load on boot + set permissions
    echo 'options v4l2loopback devices=1 video_nr=20 card_label="Cortex Scope" exclusive_caps=1' \
        | tee /etc/modprobe.d/v4l2loopback.conf >/dev/null
    echo 'v4l2loopback' | tee /etc/modules-load.d/v4l2loopback.conf >/dev/null
    if ! lsmod | grep -q v4l2loopback; then
        modprobe v4l2loopback devices=1 video_nr=20 card_label="Cortex Scope" exclusive_caps=1 || true
    fi
    echo 'KERNEL=="video20", MODE="0666"' | tee /etc/udev/rules.d/99-cortex-v4l2loopback.rules >/dev/null

    # Disable USB autosuspend for UltraSemi USB3 webcam (345f:2130)
    # The kernel suspends idle USB devices after 2s by default, and UVC cameras
    # often fail to wake properly — causing phantom disconnects.
    echo 'ACTION=="add", ATTR{idVendor}=="345f", ATTR{idProduct}=="2130", TEST=="power/control", ATTR{power/control}="on"' \
        | tee /etc/udev/rules.d/99-usb-webcam-no-suspend.rules >/dev/null
    log_ok "USB webcam autosuspend disabled (udev rule for 345f:2130)"

    udevadm control --reload-rules 2>/dev/null || true
    udevadm trigger 2>/dev/null || true
    log_ok "v4l2loopback configured (/dev/video20, auto-load on boot)"
else
    log_step "Step 1/9: Skipping dependencies (--skip-deps)"
fi

#-------------------------------------------------------------------------------
# Step 2: Download artifacts
#-------------------------------------------------------------------------------
log_step "Step 2/9: Downloading artifacts"

mkdir -p "$DOWNLOAD_DIR"

# Helper: download a file and verify its SHA256 checksum
download_and_verify() {
    local url="$1" dest="$2" expected_sha="$3" label="$4"
    log "Downloading ${label}..."
    curl -fSL --progress-bar -o "$dest" "$url" || { log_err "Failed to download ${label}"; exit 1; }
    if [[ -n "$expected_sha" ]]; then
        local actual_sha="sha256:$(sha256sum "$dest" | cut -d' ' -f1)"
        if [[ "$actual_sha" != "$expected_sha" ]]; then
            log_err "Checksum mismatch for ${label}"
            log_err "  Expected: ${expected_sha}"
            log_err "  Got:      ${actual_sha}"
            rm -f "$dest"
            exit 1
        fi
        log_ok "Checksum verified for ${label}"
    fi
    local fsize; fsize=$(du -sh "$dest" | cut -f1)
    log_ok "Downloaded ${label} (${fsize})"
}

# Helper: check L4T major version compatibility (warn on minor mismatch, fail on major)
check_platform_compatibility() {
    [[ -f "${DOWNLOAD_DIR}/manifest.json" ]] || return 0
    command -v jq &>/dev/null || return 0

    local manifest_l4t
    manifest_l4t=$(jq -r '.l4t_version // empty' "${DOWNLOAD_DIR}/manifest.json")
    [[ -z "$manifest_l4t" || "$manifest_l4t" == "unknown" ]] && return 0

    local local_l4t
    local_l4t=$(sed -n 's/^# R\([0-9]*\).*REVISION: \([0-9.]*\).*/\1.\2/p' /etc/nv_tegra_release 2>/dev/null || echo "")
    [[ -z "$local_l4t" ]] && return 0

    # Extract major version (e.g. "38" from "38.1.0")
    local manifest_major="${manifest_l4t%%.*}"
    local local_major="${local_l4t%%.*}"

    if [[ "$local_major" != "$manifest_major" ]]; then
        log_err "Platform mismatch: build is for L4T R${manifest_major} but this device runs L4T R${local_major}"
        echo "  SDK libs and TensorRT engines are not compatible across major L4T versions."
        exit 1
    fi

    if [[ "$local_l4t" != "$manifest_l4t" ]]; then
        log_warn "L4T minor version differs: build=${manifest_l4t}, device=${local_l4t}"
        echo "  This is usually fine. TensorRT engines may need rebuilding (run --build-engines after install)."
    else
        log_ok "Platform check passed (L4T ${local_l4t})"
    fi
}

if $USE_LICENSE_KEY; then
    # ── License key mode: redeem key via API, download with pre-signed URLs ──
    log "Redeeming license key..."
    HTTP_CODE=$(curl -s -o /tmp/cortex-redeem-response.json -w "%{http_code}" \
        -X POST "${API_URL}/install-keys/redeem" \
        -H "Content-Type: application/json" \
        -d "{\"key\": \"${INSTALL_KEY}\", \"hostname\": \"$(hostname -s)\"}")
    REDEEM_RESPONSE=$(cat /tmp/cortex-redeem-response.json 2>/dev/null || echo "")
    rm -f /tmp/cortex-redeem-response.json

    if [[ "$HTTP_CODE" != "200" ]]; then
        ERR_MSG=$(echo "$REDEEM_RESPONSE" | jq -r '.error // empty' 2>/dev/null)
        if [[ -z "$ERR_MSG" ]]; then
            log_err "License key validation failed (HTTP ${HTTP_CODE})"
        else
            log_err "License key validation failed: ${ERR_MSG}"
        fi
        exit 1
    fi

    # Verify response is valid JSON with expected fields
    if ! echo "$REDEEM_RESPONSE" | jq -e '.status == "ok" and .tenant_id and .downloads.app.url' &>/dev/null; then
        log_err "Invalid response from server — missing required fields"
        exit 1
    fi

    # Extract fields
    TENANT_ID=$(echo "$REDEEM_RESPONSE" | jq -r '.tenant_id')
    TENANT_NAME=$(echo "$REDEEM_RESPONSE" | jq -r '.tenant_name // .tenant_id')
    log_ok "Key valid — tenant: ${TENANT_NAME} (${TENANT_ID})"

    APP_URL=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.app.url')
    APP_SHA=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.app.checksum // empty')
    SDK_URL=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.sdk.url // empty')
    SDK_SHA=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.sdk.checksum // empty')
    MODELS_URL=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.models.url // empty')
    MODELS_SHA=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.models.checksum // empty')
    MANIFEST_URL=$(echo "$REDEEM_RESPONSE" | jq -r '.downloads.manifest.url // empty')

    # Extract IoT config (provisioned server-side by Lambda)
    IOT_THING_NAME=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.thing_name // empty')
    IOT_CERT_PEM=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.certificate_pem // empty')
    IOT_PRIVATE_KEY=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.private_key // empty')
    IOT_ENDPOINT=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.endpoint // empty')
    IOT_CRED_ENDPOINT=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.credential_endpoint // empty')
    IOT_ROLE_ALIAS=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.role_alias // empty')
    IOT_CA_URL=$(echo "$REDEEM_RESPONSE" | jq -r '.iot.ca_pem_url // empty')
    if [[ -n "$IOT_THING_NAME" ]] && [[ -n "$IOT_CERT_PEM" ]]; then
        log_ok "IoT certificate received from server (thing: ${IOT_THING_NAME})"
    else
        log_warn "No IoT config in server response — will try CLI provisioning in Step 8"
        log_warn "If CLI provisioning also fails, run install again with the cortex-device-provisioning AWS profile configured"
    fi

    # Download manifest first (needed for platform compatibility check)
    if [[ -n "$MANIFEST_URL" ]]; then
        curl -sfSL -o "${DOWNLOAD_DIR}/manifest.json" "$MANIFEST_URL" 2>/dev/null || true
    fi

    # Platform check before downloading tarballs
    check_platform_compatibility

    # Download app tarball (always)
    download_and_verify "$APP_URL" "${DOWNLOAD_DIR}/${APP_TARBALL}" "$APP_SHA" "app"

    # Download SDK tarball
    if ! $SKIP_SDK && [[ -n "$SDK_URL" ]]; then
        download_and_verify "$SDK_URL" "${DOWNLOAD_DIR}/${SDK_TARBALL}" "$SDK_SHA" "SDK"
    fi

    # Download models tarball
    if ! $SKIP_MODELS && [[ -n "$MODELS_URL" ]]; then
        download_and_verify "$MODELS_URL" "${DOWNLOAD_DIR}/${MODELS_TARBALL}" "$MODELS_SHA" "models"
    fi

else
    # ── AWS CLI mode (--update on enrolled device) ──
    # Build profile/credential args — empty if using IoT env vars
    AWS_ARGS="--region $AWS_REGION"
    [[ -n "$AWS_PROFILE" ]] && AWS_ARGS="--profile $AWS_PROFILE $AWS_ARGS"

    AWS_CLI_BIN=""
    for p in /usr/local/bin/aws /usr/bin/aws /home/*/.local/bin/aws; do
        [[ -x "$p" ]] && AWS_CLI_BIN="$p" && break
    done
    [[ -z "$AWS_CLI_BIN" ]] && { log_err "AWS CLI not found"; exit 1; }

    log "Downloading manifest..."
    "$AWS_CLI_BIN" s3 cp "${S3_PREFIX}/manifest.json" "${DOWNLOAD_DIR}/manifest.json" \
        $AWS_ARGS 2>/dev/null || true

    if [[ -f "${DOWNLOAD_DIR}/manifest.json" ]]; then
        echo -e "  Build info:"
        cat "${DOWNLOAD_DIR}/manifest.json" | while IFS= read -r line; do echo "    $line"; done
    fi

    # Platform check before downloading tarballs
    check_platform_compatibility

    log "Downloading ${APP_TARBALL}..."
    "$AWS_CLI_BIN" s3 cp "${S3_PREFIX}/${APP_TARBALL}" "${DOWNLOAD_DIR}/${APP_TARBALL}" \
        $AWS_ARGS
    app_size=$(du -sh "${DOWNLOAD_DIR}/${APP_TARBALL}" | cut -f1)
    log_ok "Downloaded ${APP_TARBALL} (${app_size})"

    if ! $SKIP_SDK; then
        log "Downloading ${SDK_TARBALL}..."
        "$AWS_CLI_BIN" s3 cp "${S3_PREFIX}/${SDK_TARBALL}" "${DOWNLOAD_DIR}/${SDK_TARBALL}" \
            $AWS_ARGS
        sdk_size=$(du -sh "${DOWNLOAD_DIR}/${SDK_TARBALL}" | cut -f1)
        log_ok "Downloaded ${SDK_TARBALL} (${sdk_size})"
    fi

    if ! $SKIP_MODELS; then
        if "$AWS_CLI_BIN" s3 ls "${S3_PREFIX}/${MODELS_TARBALL}" \
            $AWS_ARGS &>/dev/null; then
            log "Downloading ${MODELS_TARBALL}..."
            "$AWS_CLI_BIN" s3 cp "${S3_PREFIX}/${MODELS_TARBALL}" "${DOWNLOAD_DIR}/${MODELS_TARBALL}" \
                $AWS_ARGS
            models_size=$(du -sh "${DOWNLOAD_DIR}/${MODELS_TARBALL}" | cut -f1)
            log_ok "Downloaded ${MODELS_TARBALL} (${models_size})"
        else
            log_warn "No models tarball found in S3, skipping"
        fi
    else
        log "Skipping models download (--skip-models)"
    fi
fi  # end download mode branch

# (platform check was moved into check_platform_compatibility, called above)

#-------------------------------------------------------------------------------
# Step 3: Extract app artifacts
#-------------------------------------------------------------------------------
log_step "Step 3/9: Installing app to ${INSTALL_PREFIX}/"

# Stop running services before overwriting binaries
for svc in cortex-casemgmt cortex-uploader cortex-ui cortex-video-core; do
    if systemctl is-active "$svc" &>/dev/null; then
        log "Stopping ${svc} before update..."
        systemctl stop "$svc" || true
    fi
done
# Also kill any interactively-launched cortex processes
pkill -f "bin/cortex_" 2>/dev/null || true
sleep 1

mkdir -p "$INSTALL_PREFIX"

# Extract app (bin, lib, share)
tar -xzf "${DOWNLOAD_DIR}/${APP_TARBALL}" -C "$INSTALL_PREFIX"
bin_count=$(find "${INSTALL_PREFIX}/bin" -type f -executable 2>/dev/null | wc -l)
lib_count=$(find "${INSTALL_PREFIX}/lib" -name "*.so*" -type f 2>/dev/null | wc -l)
log_ok "Installed ${bin_count} binaries, ${lib_count} libs to ${INSTALL_PREFIX}/"

# Copy manifest
[[ -f "${DOWNLOAD_DIR}/manifest.json" ]] && cp "${DOWNLOAD_DIR}/manifest.json" "${INSTALL_PREFIX}/manifest.json"

# Write tenant binding from license key (only on fresh install, not --update)
if $USE_LICENSE_KEY && [[ "$MODE" != "update" ]]; then
    mkdir -p "${DEVICE_CONF_DIR}"
    # device_id: use IoT thing name if available (provisioned by Lambda), else hostname
    DEVICE_ID="${IOT_THING_NAME:-cortex-$(hostname -s | tr -dc 'a-zA-Z0-9_-')}"
    cat > "${DEVICE_CONF}" << CONFEOF
# Cortex device configuration — written by license key installer
device_id=${DEVICE_ID}
tenant_id=${TENANT_ID}
tenant_name=${TENANT_NAME}
aws_region=eu-central-1
api_url=${API_URL}
s3_bucket_cases=verus-case-assets
s3_bucket_releases=verus-cortex-releases
installed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
install_key=${INSTALL_KEY}
CONFEOF
    chown "${DEPLOY_USER}:${DEPLOY_GROUP}" "${DEVICE_CONF}"
    log_ok "Wrote ${DEVICE_CONF} (tenant: ${TENANT_NAME})"
fi

#-------------------------------------------------------------------------------
# Step 4: Extract SDK libs (if downloaded)
#-------------------------------------------------------------------------------
if [[ -f "${DOWNLOAD_DIR}/${SDK_TARBALL}" ]]; then
    log_step "Step 4/9: Installing SDK libraries"

    SDK_EXTRACT="/tmp/cortex-sdk-extract"
    rm -rf "$SDK_EXTRACT"
    mkdir -p "$SDK_EXTRACT"
    tar -xzf "${DOWNLOAD_DIR}/${SDK_TARBALL}" -C "$SDK_EXTRACT"

    # Holoscan SDK libs
    if [[ -d "${SDK_EXTRACT}/holoscan-lib" ]]; then
        mkdir -p "${HOST_HOLOSCAN}"
        rm -rf "${HOST_HOLOSCAN}/lib"
        mv "${SDK_EXTRACT}/holoscan-lib" "${HOST_HOLOSCAN}/lib"
        count=$(find "${HOST_HOLOSCAN}/lib" -name "*.so*" -type f 2>/dev/null | wc -l)
        log_ok "Holoscan SDK: ${count} libs → ${HOST_HOLOSCAN}/lib/"
    fi

    # CUDA libs
    if [[ -d "${SDK_EXTRACT}/cuda-lib" ]]; then
        mkdir -p "${HOST_CUDA_LIBS}"
        cp -a "${SDK_EXTRACT}/cuda-lib/"* "${HOST_CUDA_LIBS}/"
        count=$(find "${HOST_CUDA_LIBS}" -type f -o -type l 2>/dev/null | wc -l)
        log_ok "CUDA: ${count} libs → ${HOST_CUDA_LIBS}/"
    fi

    # TensorRT libs
    if [[ -d "${SDK_EXTRACT}/tensorrt-lib" ]]; then
        mkdir -p "${HOST_TENSORRT_LIBS}"
        cp -a "${SDK_EXTRACT}/tensorrt-lib/"* "${HOST_TENSORRT_LIBS}/"
        count=$(find "${HOST_TENSORRT_LIBS}" -type f -o -type l 2>/dev/null | wc -l)
        log_ok "TensorRT: ${count} libs → ${HOST_TENSORRT_LIBS}/"
    fi

    # FFmpeg 7.x libs (bundled because target apt only has FFmpeg 6.x)
    if [[ -d "${SDK_EXTRACT}/ffmpeg-lib" ]]; then
        cp -a "${SDK_EXTRACT}/ffmpeg-lib/"* "${INSTALL_PREFIX}/lib/"
        count=$(find "${SDK_EXTRACT}/ffmpeg-lib" -type f -o -type l 2>/dev/null | wc -l)
        log_ok "FFmpeg 7.x: ${count} libs → ${INSTALL_PREFIX}/lib/"
    fi

    rm -rf "$SDK_EXTRACT"
else
    log_step "Step 4/9: Skipping SDK install (not downloaded)"
fi

# Extract models (if downloaded)
if [[ -f "${DOWNLOAD_DIR}/${MODELS_TARBALL}" ]]; then
    log "Installing AI models..."
    tar -xzf "${DOWNLOAD_DIR}/${MODELS_TARBALL}" -C "$INSTALL_PREFIX"
    log_ok "Models installed to ${INSTALL_PREFIX}/models/"
fi

#-------------------------------------------------------------------------------
# Step 5: Configure dynamic linker
#-------------------------------------------------------------------------------
log_step "Step 5/9: Configuring dynamic linker"

LIBRARY_PATHS=(
    "${INSTALL_PREFIX}/lib"
    "${HOST_HOLOSCAN}/lib"
    "${HOST_CUDA_LIBS}"
    "${HOST_TENSORRT_LIBS}"
    "/usr/local/cuda/targets/sbsa-linux/lib"
)

printf '%s\n' "${LIBRARY_PATHS[@]}" | tee "$LD_CONF_FILE" >/dev/null
ldconfig
log_ok "Wrote ${LD_CONF_FILE} and ran ldconfig"

#-------------------------------------------------------------------------------
# Step 6: Configure host (NvSciIpc, permissions, directories)
#-------------------------------------------------------------------------------
log_step "Step 6/9: Configuring host"

# Recording storage directory
if [[ ! -d /var/recordings ]]; then
    mkdir -p /var/recordings
    chown "${DEPLOY_USER}:${DEPLOY_GROUP}" /var/recordings
    log_ok "Created /var/recordings (owner: ${DEPLOY_USER})"
elif [[ ! -w /var/recordings ]]; then
    chown -R "${DEPLOY_USER}:${DEPLOY_GROUP}" /var/recordings
    log_ok "Fixed /var/recordings ownership"
fi

# CAP_SYS_NICE for real-time scheduling (avoids needing sudo at runtime)
if [[ -x "${INSTALL_PREFIX}/bin/cortex_core_video" ]]; then
    setcap cap_sys_nice=ep "${INSTALL_PREFIX}/bin/cortex_core_video" 2>/dev/null && \
        log_ok "Granted CAP_SYS_NICE to cortex_core_video" || \
        log_warn "Could not set capabilities (setcap not available)"
fi

# NvSciIpc endpoints for multi-process video
# Uses a version marker to detect stale configs and re-provision the full set.
# Bump NVSCI_CFG_VERSION when endpoints change so --update picks up additions.
NVSCI_CFG_VERSION="cortex-nvsci-v3"
if [[ -f "$NVSCIIPC_CFG" ]]; then
    if grep -q "$NVSCI_CFG_VERSION" "$NVSCIIPC_CFG"; then
        log_ok "NvSciIpc endpoints up to date (${NVSCI_CFG_VERSION})"
    else
        # Remove any previous Cortex endpoint block before re-adding
        sed -i '/# Cortex compositor/,/# --- end cortex endpoints ---/d' "$NVSCIIPC_CFG"
        sed -i '/# Cortex source/,/# --- end cortex endpoints ---/d' "$NVSCIIPC_CFG"
        # Also remove individual stale entries that predate the block markers
        sed -i '/nvscistream_10[0-9]/d; /nvscistream_11[01]/d' "$NVSCIIPC_CFG"
        sed -i '/nvscistream_20[0-9]/d; /nvscistream_21[01]/d' "$NVSCIIPC_CFG"
        sed -i '/nvscistream_30[0-9]/d; /nvscistream_31[01]/d' "$NVSCIIPC_CFG"
        sed -i '/nvscistream_40[0-9]/d; /nvscistream_41[01]/d' "$NVSCIIPC_CFG"
        sed -i '/Cortex source capture endpoints/d' "$NVSCIIPC_CFG"
        # Remove stale compositor entries (0-9 range, only the ones we own)
        sed -i '/^INTER_PROCESS.*nvscistream_0 /d; /^INTER_PROCESS.*nvscistream_6 /d; /^INTER_PROCESS.*nvscistream_8 /d' "$NVSCIIPC_CFG"

        cat >> "$NVSCIIPC_CFG" << NVSCIEOF
# Cortex NvSciStream endpoints — ${NVSCI_CFG_VERSION}
# Compositor → downstream services (recorder, teleconf, NDI stream)
INTER_PROCESS   nvscistream_0        nvscistream_1     16  24576
INTER_PROCESS   nvscistream_6        nvscistream_7     16  24576
INTER_PROCESS   nvscistream_8        nvscistream_9     16  24576
# Source A (100-111): display, rec, deid, tseg, tele, ndi
INTER_PROCESS   nvscistream_100      nvscistream_101   16  24576
INTER_PROCESS   nvscistream_102      nvscistream_103   16  24576
INTER_PROCESS   nvscistream_104      nvscistream_105   16  24576
INTER_PROCESS   nvscistream_106      nvscistream_107   16  24576
INTER_PROCESS   nvscistream_108      nvscistream_109   16  24576
INTER_PROCESS   nvscistream_110      nvscistream_111   16  24576
# Source B (200-211)
INTER_PROCESS   nvscistream_200      nvscistream_201   16  24576
INTER_PROCESS   nvscistream_202      nvscistream_203   16  24576
INTER_PROCESS   nvscistream_204      nvscistream_205   16  24576
INTER_PROCESS   nvscistream_206      nvscistream_207   16  24576
INTER_PROCESS   nvscistream_208      nvscistream_209   16  24576
INTER_PROCESS   nvscistream_210      nvscistream_211   16  24576
# Source C (300-311)
INTER_PROCESS   nvscistream_300      nvscistream_301   16  24576
INTER_PROCESS   nvscistream_302      nvscistream_303   16  24576
INTER_PROCESS   nvscistream_304      nvscistream_305   16  24576
INTER_PROCESS   nvscistream_306      nvscistream_307   16  24576
INTER_PROCESS   nvscistream_308      nvscistream_309   16  24576
INTER_PROCESS   nvscistream_310      nvscistream_311   16  24576
# Source D (400-411)
INTER_PROCESS   nvscistream_400      nvscistream_401   16  24576
INTER_PROCESS   nvscistream_402      nvscistream_403   16  24576
INTER_PROCESS   nvscistream_404      nvscistream_405   16  24576
INTER_PROCESS   nvscistream_406      nvscistream_407   16  24576
INTER_PROCESS   nvscistream_408      nvscistream_409   16  24576
INTER_PROCESS   nvscistream_410      nvscistream_411   16  24576
# --- end cortex endpoints ---
NVSCIEOF

        log_ok "Provisioned NvSciIpc endpoints (0-9, 100-411) [${NVSCI_CFG_VERSION}]"
        echo -e "  ${YELLOW}NOTE: Reboot required for NvSciIpc kernel module to load new endpoints${NC}"
        NEEDS_REBOOT=true
    fi
else
    log_warn "${NVSCIIPC_CFG} not found (nvidia-l4t-nvsci not installed?)"
fi

#-------------------------------------------------------------------------------
# Step 7: Install systemd services
#-------------------------------------------------------------------------------
log_step "Step 7/9: Installing systemd services"
{
    LD_LIB_PATH="${INSTALL_PREFIX}/lib:${HOST_HOLOSCAN}/lib:${HOST_CUDA_LIBS}:${HOST_TENSORRT_LIBS}:/usr/local/cuda/targets/sbsa-linux/lib"

    # cortex-casemgmt.service — always-on case database + gRPC server
    tee "${SYSTEMD_DIR}/cortex-casemgmt.service" >/dev/null << SVCEOF
[Unit]
Description=Cortex Case Management
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=${DEPLOY_USER}
Group=${DEPLOY_GROUP}

Environment="LD_LIBRARY_PATH=${LD_LIB_PATH}"
WorkingDirectory=${INSTALL_PREFIX}/bin
ExecStart=${INSTALL_PREFIX}/bin/cortex_core_casemgmt

Restart=always
RestartSec=2s
StartLimitBurst=5
StartLimitIntervalSec=300

StandardOutput=journal
StandardError=journal
SyslogIdentifier=cortex-casemgmt

[Install]
WantedBy=multi-user.target
SVCEOF
    log_ok "Installed cortex-casemgmt.service"

    # cortex-uploader.service — always-on cloud sync (case upload, heartbeat)
    tee "${SYSTEMD_DIR}/cortex-uploader.service" >/dev/null << SVCEOF
[Unit]
Description=Cortex Case Uploader
After=network-online.target cortex-casemgmt.service
Wants=network-online.target cortex-casemgmt.service

[Service]
Type=simple
User=${DEPLOY_USER}
Group=${DEPLOY_GROUP}

Environment="LD_LIBRARY_PATH=${LD_LIB_PATH}"
WorkingDirectory=${INSTALL_PREFIX}/bin
ExecStart=${INSTALL_PREFIX}/bin/cortex_app_uploader

Restart=always
RestartSec=5s
StartLimitBurst=5
StartLimitIntervalSec=300

StandardOutput=journal
StandardError=journal
SyslogIdentifier=cortex-uploader

[Install]
WantedBy=multi-user.target
SVCEOF
    log_ok "Installed cortex-uploader.service"

    # Remove old video-core/ui services if they exist (replaced by run_multiprocess.sh)
    for old_svc in cortex-video-core cortex-ui; do
        if [[ -f "${SYSTEMD_DIR}/${old_svc}.service" ]]; then
            systemctl disable "${old_svc}" 2>/dev/null || true
            rm -f "${SYSTEMD_DIR}/${old_svc}.service"
            log_ok "Removed legacy ${old_svc}.service"
        fi
    done

    systemctl daemon-reload
    systemctl enable cortex-casemgmt cortex-uploader 2>/dev/null || true
    systemctl restart cortex-casemgmt cortex-uploader 2>/dev/null || true
    log_ok "systemd services enabled and started"
}

#-------------------------------------------------------------------------------
# Step 8/9: Set up IoT Core (certificate + config)
#-------------------------------------------------------------------------------
# IoT provisioning happens in two ways:
#   a) License key install: Lambda provisions cert + thing server-side, returns
#      cert/key in the redeem response. No AWS CLI needed on device.
#   b) Manual/fallback: Use cortex-device-provisioning IAM user to call AWS CLI.
#
# After provisioning, the device uses ONLY its X.509 cert for all AWS access
# (via IoT Credential Provider). No IAM access keys remain on the device.

log_step "Step 8/9: Setting up IoT Core connectivity"
{  # Step 8 block

    IOT_CERT_DIR="${INSTALL_PREFIX}/certs"

    if [[ ! -f "${IOT_CERT_DIR}/device.pem.crt" ]]; then
        mkdir -p "${IOT_CERT_DIR}"

        # Method A: Check if license key redeem already provided IoT config
        if [[ -n "${IOT_THING_NAME:-}" ]] && [[ -n "${IOT_CERT_PEM:-}" ]]; then
            # Cert + key were returned by Lambda during license key redemption
            printf '%s\n' "$IOT_CERT_PEM" > "${IOT_CERT_DIR}/device.pem.crt"
            printf '%s\n' "$IOT_PRIVATE_KEY" > "${IOT_CERT_DIR}/device.private.key"
            curl -s -o "${IOT_CERT_DIR}/AmazonRootCA1.pem" "${IOT_CA_URL:-https://www.amazontrust.com/repository/AmazonRootCA1.pem}"
            chmod 600 "${IOT_CERT_DIR}/device.private.key"
            chown -R "${DEPLOY_USER}:${DEPLOY_GROUP}" "${IOT_CERT_DIR}"

            # Write IoT config to device.conf
            mkdir -p "${DEVICE_CONF_DIR}"
            [[ -f "${DEVICE_CONF}" ]] && sed -i '/^iot_/d' "${DEVICE_CONF}"
            cat >> "${DEVICE_CONF}" << IOTEOF

# IoT Core
iot_endpoint=${IOT_ENDPOINT:-iot.verussurgical.com}
iot_thing_name=${IOT_THING_NAME}
iot_cert_path=${IOT_CERT_DIR}/device.pem.crt
iot_key_path=${IOT_CERT_DIR}/device.private.key
iot_ca_path=${IOT_CERT_DIR}/AmazonRootCA1.pem
iot_credential_endpoint=${IOT_CRED_ENDPOINT:-cj9ykke9k8lm6.credentials.iot.eu-central-1.amazonaws.com}
iot_role_alias=${IOT_ROLE_ALIAS:-cortex-device-s3-alias}
IOTEOF
            log_ok "IoT configured from license key (thing: ${IOT_THING_NAME})"

        else
            # Method B: Use cortex-device-provisioning IAM user via AWS CLI
            AWS_CLI=""
            for p in /usr/local/bin/aws /usr/bin/aws /home/*/.local/bin/aws; do
                [[ -x "$p" ]] && AWS_CLI="$p" && break
            done

            if [[ -z "$AWS_CLI" ]]; then
                log_warn "AWS CLI not found — skipping IoT setup"
            else
                REAL_HOME=$(eval echo "~${DEPLOY_USER}")
                AWS_CREDS_FILE="${REAL_HOME}/.aws/credentials"

                # Use the provisioning-only profile (minimal permissions)
                IOT_PROFILE=""
                for profile in cortex-device-provisioning cortex-teleconf-dev; do
                    if grep -q "\[${profile}\]" "$AWS_CREDS_FILE" 2>/dev/null; then
                        IOT_PROFILE="$profile"
                        break
                    fi
                done

                if [[ -z "$IOT_PROFILE" ]]; then
                    log_warn "No provisioning AWS profile found — skipping IoT setup"
                else
                    THING_NAME=""
                    [[ -f "${DEVICE_CONF}" ]] && THING_NAME=$(grep '^device_id=' "${DEVICE_CONF}" 2>/dev/null | cut -d= -f2)
                    [[ -z "$THING_NAME" ]] && THING_NAME="cortex-$(hostname -s | tr -dc 'a-zA-Z0-9_-')"

                    log "Provisioning IoT thing: ${THING_NAME} (profile: ${IOT_PROFILE})"

                    export AWS_SHARED_CREDENTIALS_FILE="$AWS_CREDS_FILE"
                    export AWS_CONFIG_FILE="${REAL_HOME}/.aws/config"

                    CERT_JSON=$("$AWS_CLI" --profile "$IOT_PROFILE" iot create-keys-and-certificate \
                        --set-as-active \
                        --certificate-pem-outfile "${IOT_CERT_DIR}/device.pem.crt" \
                        --public-key-outfile "${IOT_CERT_DIR}/device.public.key" \
                        --private-key-outfile "${IOT_CERT_DIR}/device.private.key" \
                        --region eu-central-1 2>/dev/null) || true

                    CERT_ARN=$(echo "$CERT_JSON" | grep -o '"certificateArn"[[:space:]]*:[[:space:]]*"[^"]*"' | grep -o '"arn:[^"]*"' | tr -d '"')

                    if [[ -f "${IOT_CERT_DIR}/device.pem.crt" ]] && [[ -n "$CERT_ARN" ]]; then
                        chmod 600 "${IOT_CERT_DIR}/device.private.key"
                        chown -R "${DEPLOY_USER}:${DEPLOY_GROUP}" "${IOT_CERT_DIR}"
                        curl -s -o "${IOT_CERT_DIR}/AmazonRootCA1.pem" https://www.amazontrust.com/repository/AmazonRootCA1.pem

                        "$AWS_CLI" --profile "$IOT_PROFILE" iot create-thing \
                            --thing-name "${THING_NAME}" --thing-type-name cortex-device \
                            --region eu-central-1 2>/dev/null || true
                        "$AWS_CLI" --profile "$IOT_PROFILE" iot attach-policy \
                            --policy-name cortex-device-policy --target "${CERT_ARN}" \
                            --region eu-central-1 2>/dev/null || true
                        "$AWS_CLI" --profile "$IOT_PROFILE" iot attach-thing-principal \
                            --thing-name "${THING_NAME}" --principal "${CERT_ARN}" \
                            --region eu-central-1 2>/dev/null || true

                        mkdir -p "${DEVICE_CONF_DIR}"
                        [[ -f "${DEVICE_CONF}" ]] && sed -i '/^iot_/d' "${DEVICE_CONF}"
                        cat >> "${DEVICE_CONF}" << IOTEOF

# IoT Core
iot_endpoint=iot.verussurgical.com
iot_thing_name=${THING_NAME}
iot_cert_path=${IOT_CERT_DIR}/device.pem.crt
iot_key_path=${IOT_CERT_DIR}/device.private.key
iot_ca_path=${IOT_CERT_DIR}/AmazonRootCA1.pem
iot_credential_endpoint=cj9ykke9k8lm6.credentials.iot.eu-central-1.amazonaws.com
iot_role_alias=cortex-device-s3-alias
IOTEOF
                        log_ok "IoT provisioned via CLI (thing: ${THING_NAME})"
                    else
                        log_warn "IoT certificate generation failed"
                        rm -f "${IOT_CERT_DIR}/device.pem.crt" "${IOT_CERT_DIR}/device.public.key" \
                              "${IOT_CERT_DIR}/device.private.key" 2>/dev/null
                    fi

                    unset AWS_SHARED_CREDENTIALS_FILE AWS_CONFIG_FILE
                fi
            fi
        fi
    else
        log_ok "IoT certificate already exists"
    fi
}  # end Step 8 block

#-------------------------------------------------------------------------------
# Step 9/9: Install remote desktop (x11vnc + websockify)
#-------------------------------------------------------------------------------
log_step "Step 9/9: Installing remote desktop services (x11vnc + websockify)"
SCRIPT_DIR_VNC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -x "${SCRIPT_DIR_VNC}/install_vnc.sh" ]]; then
    "${SCRIPT_DIR_VNC}/install_vnc.sh"
    log_ok "Remote desktop services installed"
else
    log_warn "install_vnc.sh not found — remote desktop not set up"
fi

#-------------------------------------------------------------------------------
# Optional: Build TensorRT engines
#-------------------------------------------------------------------------------
if $BUILD_ENGINES; then
    log_step "Building TensorRT engines (this may take 5-10 minutes)..."
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    if [[ -x "${SCRIPT_DIR}/build_models.sh" ]]; then
        "${SCRIPT_DIR}/build_models.sh"
    else
        log_warn "build_models.sh not found — run it manually after install"
    fi
fi

#-------------------------------------------------------------------------------
# Cleanup
#-------------------------------------------------------------------------------
rm -rf "$DOWNLOAD_DIR"
log_ok "Cleaned up download cache"

#-------------------------------------------------------------------------------
# Summary
#-------------------------------------------------------------------------------
echo ""
echo -e "${BOLD}════════════════════════════════════════════${NC}"
echo -e "${BOLD}  Cortex Installation Complete${NC}"
echo -e "${BOLD}════════════════════════════════════════════${NC}"
echo ""
echo -e "  Install prefix:  ${GREEN}${INSTALL_PREFIX}${NC}"

if [[ -d "${INSTALL_PREFIX}/bin" ]]; then
    echo "  Binaries:"
    for bin in "${INSTALL_PREFIX}/bin/"cortex*; do
        [[ -x "$bin" ]] && echo "    $(basename "$bin")"
    done
fi

echo ""

if $NEEDS_REBOOT; then
    echo -e "  ${YELLOW}${BOLD}⚠  REBOOT REQUIRED${NC}"
    echo -e "  ${YELLOW}NvSciIpc endpoints were added. The kernel module${NC}"
    echo -e "  ${YELLOW}reads /etc/nvsciipc.cfg only at boot.${NC}"
    echo -e "  ${YELLOW}Run: sudo reboot${NC}"
    echo ""
fi

# Restart background services after update
if [[ "$MODE" == "update" ]] && ! $NEEDS_REBOOT; then
    for svc in cortex-casemgmt cortex-uploader; do
        if systemctl is-enabled "$svc" &>/dev/null; then
            log "Restarting ${svc}..."
            systemctl restart "$svc" || true
        fi
    done
    echo -e "  ${GREEN}Background services restarted after update${NC}"
    echo ""
fi

echo -e "  ${BOLD}Background services (auto-start on boot):${NC}"
echo "    systemctl status cortex-casemgmt cortex-uploader"
echo ""
echo -e "  ${BOLD}Start the video pipeline + UI:${NC}"
echo "    ${INSTALL_PREFIX}/bin/run_multiprocess.sh"
echo ""
echo -e "  ${BOLD}View logs:${NC}"
echo "    journalctl -u cortex-casemgmt -f"
echo "    journalctl -u cortex-uploader -f"

if [[ ! -f "${INSTALL_PREFIX}/models/oob/anonymization_model.engine" ]] 2>/dev/null; then
    echo ""
    echo -e "  ${YELLOW}TensorRT engines not built yet. Run:${NC}"
    echo "    sudo ./scripts/build_models.sh"
fi
