#!/bin/bash
# Cortiva Node Installer
# https://install.cortiva.dev
#
# Usage:
#   curl -fsSL https://install.cortiva.dev | bash
#   curl -fsSL https://install.cortiva.dev | bash -s -- --key ctv_node_xxx
#   curl -fsSL https://install.cortiva.dev | bash -s -- --dry-run
set -e

# --- Args ---

DRY_RUN=false
PAIRING_KEY=""
TOKEN_ID=""
while [ $# -gt 0 ]; do
    case "$1" in
        --dry-run) DRY_RUN=true; shift ;;
        --key) PAIRING_KEY="$2"; shift 2 ;;
        --key=*) PAIRING_KEY="${1#*=}"; shift ;;
        --token-id) TOKEN_ID="$2"; shift 2 ;;
        --token-id=*) TOKEN_ID="${1#*=}"; shift ;;
        *) shift ;;
    esac
done

# --- Colours & helpers ---

BOLD="\033[1m"
DIM="\033[2m"
RESET="\033[0m"
GREEN="\033[32m"
RED="\033[31m"
YELLOW="\033[33m"
CYAN="\033[36m"
MAGENTA="\033[35m"

ok()   { printf "  ${GREEN}✓${RESET} %s\n" "$*"; }
fail() { printf "  ${RED}✗${RESET} %s\n" "$*"; }
warn() { printf "  ${YELLOW}○${RESET} %s\n" "$*"; }
info() { printf "  ${DIM}%s${RESET}\n" "$*"; }

step() {
    printf "\n${BOLD}${CYAN}▸ %s${RESET}\n" "$*"
}

run_or_dry() {
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] $*"
    else
        eval "$@"
    fi
}

HQ_BASE="https://api.cortiva.dev"

report_progress() {
    local step_name="$1"
    local error_msg="${2:-}"
    [ -z "$TOKEN_ID" ] && return
    [ "$DRY_RUN" = true ] && return
    local payload="{\"token_id\":\"$TOKEN_ID\",\"step\":\"$step_name\""
    payload="$payload,\"hostname\":\"$(hostname -s 2>/dev/null || echo unknown)\""
    payload="$payload,\"os\":\"$OS\",\"arch\":\"$ARCH\""
    [ -n "$CPU_MODEL" ] && payload="$payload,\"cpu_model\":\"$CPU_MODEL\""
    [ -n "$CPU_CORES" ] && payload="$payload,\"cpu_cores\":$CPU_CORES"
    [ -n "$GPU_MODEL" ] && payload="$payload,\"gpu_model\":\"$GPU_MODEL\""
    [ -n "$error_msg" ] && payload="$payload,\"error\":\"$error_msg\""
    payload="$payload}"
    curl -fsS -X POST "$HQ_BASE/api/nodes/install-progress" \
        -H "Content-Type: application/json" \
        -d "$payload" >/dev/null 2>&1 || true
}

spin_install() {
    local name="$1"
    local check_cmd="$2"
    local install_cmd="$3"
    local version_cmd="$4"

    if eval "$check_cmd" &>/dev/null; then
        local version
        version=$(eval "$version_cmd" 2>/dev/null || echo "")
        if [ -n "$version" ]; then
            printf "  ${GREEN}✓${RESET} %s ${DIM}(%s)${RESET}\n" "$name" "$version"
        else
            ok "$name"
        fi
    else
        if [ "$DRY_RUN" = true ]; then
            warn "$name — would install"
        else
            printf "  ${YELLOW}◌${RESET} %s — installing..." "$name"
            if eval "$install_cmd" >/dev/null 2>&1; then
                local version
                version=$(eval "$version_cmd" 2>/dev/null || echo "")
                printf "\r  ${GREEN}✓${RESET} %s ${DIM}(%s)${RESET}          \n" "$name" "$version"
            else
                printf "\r  ${RED}✗${RESET} %s — install failed          \n" "$name"
                return 1
            fi
        fi
    fi
}

# --- Banner ---

printf "\n"
printf "  ${MAGENTA}${BOLD}cortiva${RESET}\n"
printf "  ${DIM}Node Installer${RESET}\n"
printf "  ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"

if [ "$DRY_RUN" = true ]; then
    printf "\n  ${YELLOW}${BOLD}DRY RUN${RESET} ${DIM}— no changes will be made${RESET}\n"
fi

# --- Platform detection ---

step "Checking platform"

OS="$(uname -s)"
ARCH="$(uname -m)"

case "$OS" in
    Darwin)
        MACOS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "0")
        MACOS_MAJOR=$(echo "$MACOS_VERSION" | cut -d. -f1)
        if [ "$MACOS_MAJOR" -lt 13 ] 2>/dev/null; then
            fail "macOS $MACOS_VERSION — minimum required: macOS 13 (Ventura)"
            exit 1
        fi
        ok "macOS $MACOS_VERSION ($ARCH)"
        ;;
    Linux)
        if [ -f /etc/os-release ]; then
            DISTRO=$(. /etc/os-release && echo "$PRETTY_NAME")
        else
            DISTRO="Linux"
        fi
        ok "$DISTRO ($ARCH)"
        ;;
    *)
        fail "Unsupported OS: $OS"
        info "See https://docs.cortiva.dev/getting-started/install"
        exit 1
        ;;
esac

# Disk space
FREE_KB=$(df -k / | tail -1 | awk '{print $4}')
FREE_GB=$((FREE_KB / 1024 / 1024))
if [ "$FREE_GB" -lt 10 ]; then
    fail "Only ${FREE_GB}GB free — minimum 10GB required"
    exit 1
fi
ok "${FREE_GB}GB free disk space"

# Detect CPU
CPU_MODEL=""
CPU_CORES=""
if [ "$OS" = "Darwin" ]; then
    CPU_MODEL=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo "")
    CPU_CORES=$(sysctl -n hw.ncpu 2>/dev/null || echo "")
elif [ "$OS" = "Linux" ]; then
    CPU_MODEL=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2 | xargs || echo "")
    CPU_CORES=$(nproc 2>/dev/null || echo "")
fi
if [ -n "$CPU_MODEL" ]; then
    printf "  ${GREEN}✓${RESET} %s ${DIM}(%s cores)${RESET}\n" "$CPU_MODEL" "$CPU_CORES"
fi

# Detect GPU
GPU_MODEL=""
GPU_VRAM=""
if [ "$OS" = "Darwin" ]; then
    GPU_INFO=$(system_profiler SPDisplaysDataType 2>/dev/null | grep 'Chipset Model' | head -1 | cut -d: -f2 | xargs || echo "")
    GPU_VRAM_RAW=$(system_profiler SPDisplaysDataType 2>/dev/null | grep 'VRAM' | head -1 | cut -d: -f2 | xargs || echo "")
    if [ -n "$GPU_INFO" ]; then
        GPU_MODEL="$GPU_INFO"
        [ -n "$GPU_VRAM_RAW" ] && GPU_VRAM="$GPU_VRAM_RAW"
    fi
elif [ "$OS" = "Linux" ]; then
    if command -v nvidia-smi &>/dev/null; then
        GPU_MODEL=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "")
        GPU_VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 || echo "")
        [ -n "$GPU_VRAM" ] && GPU_VRAM="${GPU_VRAM} MB"
    elif command -v lspci &>/dev/null; then
        GPU_MODEL=$(lspci 2>/dev/null | grep -i 'vga\|3d\|display' | head -1 | sed 's/.*: //' || echo "")
    fi
fi
if [ -n "$GPU_MODEL" ]; then
    if [ -n "$GPU_VRAM" ]; then
        printf "  ${GREEN}✓${RESET} %s ${DIM}(%s)${RESET}\n" "$GPU_MODEL" "$GPU_VRAM"
    else
        ok "$GPU_MODEL"
    fi
fi

report_progress "platform"

# --- Dependencies ---

step "Installing dependencies"

report_progress "dependencies"

if [ "$OS" = "Darwin" ]; then
    spin_install \
        "Xcode CLT" \
        "xcode-select -p" \
        "xcode-select --install 2>/dev/null; echo 'Complete the Xcode CLT install dialog, then press Enter'; read" \
        "xcode-select --version"

    spin_install \
        "Homebrew" \
        "command -v brew" \
        'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \
        "brew --version | head -1"

    # Always install Python 3.13 via Homebrew — system Python (3.14+) is too new
    # for many packages to have wheels, causing pip resolution failures
    spin_install \
        "Python 3.13" \
        "/opt/homebrew/opt/python@3.13/bin/python3.13 -c 'import sys; assert sys.version_info >= (3, 11)' 2>/dev/null" \
        "HOMEBREW_NO_AUTO_UPDATE=1 brew install python@3.13 && eval \"\$(/opt/homebrew/bin/brew shellenv)\"" \
        "/opt/homebrew/opt/python@3.13/bin/python3.13 --version"

    # Force use of Homebrew Python 3.13 for the rest of the script
    export PATH="/opt/homebrew/opt/python@3.13/libexec/bin:/opt/homebrew/opt/python@3.13/bin:/opt/homebrew/bin:$PATH"

    spin_install \
        "Node.js" \
        "command -v node" \
        "HOMEBREW_NO_AUTO_UPDATE=1 brew install node" \
        "node --version"

    spin_install \
        "Ollama" \
        "command -v ollama" \
        "HOMEBREW_NO_AUTO_UPDATE=1 brew install ollama" \
        "ollama --version 2>&1 | head -1"

    spin_install \
        "Neo4j" \
        "command -v neo4j" \
        "HOMEBREW_NO_AUTO_UPDATE=1 brew install neo4j" \
        "neo4j --version 2>&1 | head -1"

elif [ "$OS" = "Linux" ]; then
    if command -v apt-get &>/dev/null; then
        PKG_MGR="apt"
        PKG_INSTALL="sudo apt-get install -y -qq"
    elif command -v dnf &>/dev/null; then
        PKG_MGR="dnf"
        PKG_INSTALL="sudo dnf install -y -q"
    elif command -v pacman &>/dev/null; then
        PKG_MGR="pacman"
        PKG_INSTALL="sudo pacman -S --noconfirm --quiet"
    else
        fail "No supported package manager found (apt, dnf, pacman)"
        exit 1
    fi
    ok "Package manager: $PKG_MGR"

    spin_install \
        "Python 3.11+" \
        "python3 -c 'import sys; assert sys.version_info >= (3, 11)'" \
        "$PKG_INSTALL python3 python3-pip python3-venv" \
        "python3 --version"

    spin_install \
        "Node.js" \
        "command -v node" \
        "$PKG_INSTALL nodejs npm" \
        "node --version"

    spin_install \
        "Ollama" \
        "command -v ollama" \
        "curl -fsSL https://ollama.ai/install.sh | sh" \
        "ollama --version 2>&1 | head -1"

    # Add Neo4j repo and install
    if [ "$PKG_MGR" = "apt" ]; then
        spin_install \
            "Neo4j" \
            "command -v neo4j" \
            "curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/neo4j.gpg && echo 'deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable latest' | sudo tee /etc/apt/sources.list.d/neo4j.list && sudo apt-get update -qq && sudo apt-get install -y -qq neo4j" \
            "neo4j --version 2>&1 | head -1"
    fi
fi

if command -v claude &>/dev/null; then
    ok "Claude Code CLI"
else
    warn "Claude Code CLI — not found ${DIM}(optional: npm i -g @anthropic-ai/claude-code)${RESET}"
fi

# --- Configure Neo4j ---

step "Configuring Neo4j"

report_progress "neo4j"

# Configure Neo4j for low-memory operation
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo "")"
if [ -d "$BREW_PREFIX/etc/neo4j" ] || [ -d "/etc/neo4j" ]; then
    if [ -n "$BREW_PREFIX" ]; then
        CORTIVA_CONF="$BREW_PREFIX/etc/neo4j/neo4j.conf.d/cortiva.conf"
    else
        CORTIVA_CONF="/etc/neo4j/neo4j.conf.d/cortiva.conf"
    fi
    run_or_dry "mkdir -p '$(dirname $CORTIVA_CONF)'"
    if [ "$DRY_RUN" = false ]; then
        cat > "$CORTIVA_CONF" <<CONF
# Cortiva Myelin — Neo4j tuning for agent memory
server.memory.heap.initial_size=512m
server.memory.heap.max_size=2g
server.memory.pagecache.size=1g
server.default_listen_address=127.0.0.1
CONF
        ok "Neo4j configured (512m-2g heap, local only)"
    fi
fi

# Generate Neo4j password
NEO4J_PASSWORD="cortiva_$(openssl rand -hex 8)"

# Set password (different methods for different Neo4j versions)
if command -v neo4j-admin &>/dev/null; then
    run_or_dry "neo4j-admin dbms set-initial-password '$NEO4J_PASSWORD' 2>/dev/null || true"
fi

# Start Neo4j service
if [ "$OS" = "Darwin" ]; then
    run_or_dry "brew services start neo4j"
else
    run_or_dry "sudo systemctl enable --now neo4j"
fi

# Wait for Neo4j to be ready
if [ "$DRY_RUN" = false ]; then
    printf "  ${YELLOW}◌${RESET} Waiting for Neo4j..."
    NEO4J_READY=false
    for i in $(seq 1 30); do
        if curl -sf http://127.0.0.1:7474 >/dev/null 2>&1; then
            printf "\r  ${GREEN}✓${RESET} Neo4j ready          \n"
            NEO4J_READY=true
            break
        fi
        sleep 1
    done
    if [ "$NEO4J_READY" = false ]; then
        printf "\r  ${YELLOW}○${RESET} Neo4j not yet responding — check 'neo4j status'          \n"
    fi
fi

# --- Install Cortiva framework + HQ ---

step "Installing Cortiva"

report_progress "installing"

CORTIVA_REPO="https://github.com/Innovology/cortiva.git"
CORTIVA_HQ_WHEEL="https://install.cortiva.dev/packages/cortiva_hq-0.1.0-py3-none-any.whl"

# Use python3 -m pip to ensure pip matches the active Python interpreter,
# not the system Xcode CLT pip which may be a different version
PIP_INSTALL="python3 -m pip install --break-system-packages --force-reinstall -q"

# Install cortiva with adapter extras (anthropic + ollama for consciousness layers)
printf "  ${YELLOW}◌${RESET} cortiva (framework + adapters) — installing..."
if run_or_dry "$PIP_INSTALL \"cortiva[anthropic,ollama] @ git+${CORTIVA_REPO}\""; then
    if [ "$DRY_RUN" = false ]; then
        fwk_ver=$(python3 -m pip show cortiva 2>/dev/null | grep Version | cut -d' ' -f2)
        printf "\r  ${GREEN}✓${RESET} cortiva ${DIM}($fwk_ver)${RESET}          \n"
    else
        printf "\n"
    fi
else
    printf "\r  ${RED}✗${RESET} cortiva install failed          \n"
    report_progress "error" "Failed to install cortiva framework"
    fail "Check your network connection and try again"
    exit 1
fi

# Install cortiva-hq (commercial layer) from pre-built wheel
printf "  ${YELLOW}◌${RESET} cortiva-hq — installing..."
if run_or_dry "$PIP_INSTALL \"${CORTIVA_HQ_WHEEL}\""; then
    if [ "$DRY_RUN" = false ]; then
        hq_ver=$(python3 -m pip show cortiva-hq 2>/dev/null | grep Version | cut -d' ' -f2)
        if [ -z "$hq_ver" ]; then
            printf "\r  ${RED}✗${RESET} cortiva-hq install failed          \n"
            report_progress "error" "cortiva-hq wheel download failed"
            fail "Check your network connection and try again"
            exit 1
        fi
        printf "\r  ${GREEN}✓${RESET} cortiva-hq ${DIM}($hq_ver)${RESET}          \n"
    else
        printf "\n"
    fi
else
    printf "\r  ${RED}✗${RESET} cortiva-hq install failed          \n"
    report_progress "error" "Failed to install cortiva-hq"
    fail "Check your network connection and try again"
    exit 1
fi

# Apple Silicon: install mlx-lm so the MLX runtime is usable out of the box.
# Without this, deployments with runtime=mlx will fail with "mlx_lm not installed".
if [ "$OS" = "Darwin" ] && [ "$ARCH" = "arm64" ]; then
    printf "  ${YELLOW}◌${RESET} mlx-lm (Apple Silicon MLX runtime) — installing..."
    if run_or_dry "$PIP_INSTALL mlx-lm"; then
        if [ "$DRY_RUN" = false ]; then
            mlx_ver=$(python3 -m pip show mlx-lm 2>/dev/null | grep Version | cut -d' ' -f2)
            printf "\r  ${GREEN}✓${RESET} mlx-lm ${DIM}($mlx_ver)${RESET}          \n"
        else
            printf "\n"
        fi
    else
        # Non-fatal — Ollama still works. User just can't use runtime=mlx.
        printf "\r  ${YELLOW}!${RESET} mlx-lm install failed (MLX runtime unavailable; Ollama still works)\n"
    fi
fi

# --- Pull default Ollama model ---

step "Preparing local model"

report_progress "model"

OLLAMA_MODEL="qwen3.5:latest"

# Start Ollama service if not already running
if command -v ollama &>/dev/null; then
    if ! curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
        if [ "$DRY_RUN" = false ]; then
            info "Starting Ollama service..."
            if [ "$OS" = "Darwin" ]; then
                open -a Ollama 2>/dev/null || ollama serve &>/dev/null &
            else
                ollama serve &>/dev/null &
            fi
            # Wait for Ollama to be ready
            for i in $(seq 1 15); do
                if curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
                    break
                fi
                sleep 1
            done
        fi
    fi

    # Check if model already pulled
    if curl -sf http://localhost:11434/api/tags 2>/dev/null | grep -q "qwen3.5"; then
        ok "Ollama model ${DIM}($OLLAMA_MODEL — already available)${RESET}"
    else
        printf "  ${YELLOW}◌${RESET} Pulling %s — this may take a few minutes..." "$OLLAMA_MODEL"
        if run_or_dry "ollama pull $OLLAMA_MODEL 2>/dev/null"; then
            printf "\r  ${GREEN}✓${RESET} %s — ready          \n" "$OLLAMA_MODEL"
        else
            printf "\r  ${YELLOW}○${RESET} %s — pull failed (agents will use API-only mode)          \n" "$OLLAMA_MODEL"
        fi
    fi
else
    warn "Ollama not available — agents will use API-only consciousness"
fi

# --- Create Cortiva workspace ---

step "Creating workspace"

report_progress "workspace"

CORTIVA_HOME="$HOME/.cortiva"
WORKSPACE="$CORTIVA_HOME/workspace"
CONFIG_FILE="$WORKSPACE/cortiva.yaml"

run_or_dry "mkdir -p '$CORTIVA_HOME/logs'"
run_or_dry "mkdir -p '$WORKSPACE/agents'"

HOSTNAME_SHORT=$(hostname -s 2>/dev/null || echo "node")

if [ -f "$CONFIG_FILE" ]; then
    warn "Existing workspace found at $WORKSPACE"
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] Would prompt: keep/fresh/skip"
    else
        echo ""
        info "Would you like to:"
        info "  [k] Keep existing workspace and re-register with HQ"
        info "  [f] Fresh workspace (backs up existing)"
        info "  [s] Skip workspace setup"
        read -rp "    Choice [k/f/s]: " choice

        case "$choice" in
            f)
                backup_dir="$CORTIVA_HOME/backup-$(date +%Y%m%d-%H%M%S)"
                mkdir -p "$backup_dir"
                cp -r "$WORKSPACE" "$backup_dir/"
                ok "Workspace backed up to $backup_dir"
                ;;
            s)
                ok "Keeping existing workspace"
                ;;
        esac
    fi
fi

# Generate cortiva.yaml (the framework's config format)
if [ ! -f "$CONFIG_FILE" ] || [ "${choice:-k}" = "f" ]; then
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] Would generate $CONFIG_FILE"
    else
        cat > "$CONFIG_FILE" <<YAML
# Cortiva Node Configuration
# Generated by install.cortiva.dev on $(date -u +%Y-%m-%dT%H:%M:%SZ)

fabric:
  name: "$HOSTNAME_SHORT"
  heartbeat_interval: 30

memory:
  adapter: myelin
  neo4j_uri: bolt://127.0.0.1:7687
  neo4j_auth:
    - neo4j
    - "$NEO4J_PASSWORD"

consciousness:
  provider: anthropic
  model: claude-sonnet-4-20250514
  budget:
    daily_limit: 1000
    per_agent_default: 50

routine:
  adapter: ollama
  model: "$OLLAMA_MODEL"

channel:
  adapter: slack
  config: {}

agents:
  directory: ./agents

# Cortiva HQ connection (managed remotely)
hq:
  portal_url: $HQ_BASE
  node_token: ""
  token_id: ""
YAML
        ok "Workspace created at $WORKSPACE"
    fi
fi

ok "Config: $CONFIG_FILE"

# --- Pairing with HQ ---

step "Pairing with Cortiva HQ"

report_progress "pairing"

if [ -z "$PAIRING_KEY" ]; then
    echo ""
    read -rp "  Enter your pairing key: " PAIRING_KEY
fi

if [ -n "$PAIRING_KEY" ]; then
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] Would save pairing key to $CONFIG_FILE"
    else
        # Update the HQ section in cortiva.yaml with the pairing key
        if command -v python3 &>/dev/null; then
            python3 -c "
import yaml
from pathlib import Path

config_path = Path('$CONFIG_FILE')
config = yaml.safe_load(config_path.read_text())
config.setdefault('hq', {})
config['hq']['node_token'] = '$PAIRING_KEY'
config['hq']['token_id'] = '$TOKEN_ID'
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
"
        fi
        ok "Paired with Cortiva HQ"
    fi
fi

# --- Start Cortiva fabric daemon ---

step "Starting Cortiva"

report_progress "starting"

if [ "$DRY_RUN" = true ]; then
    info "[dry run] Would start cortiva fabric daemon"
    info "[dry run] Would start cortiva-hq node agent"
    report_progress "done"
else
    # Start the fabric daemon (runs agents)
    printf "  ${YELLOW}◌${RESET} Starting fabric daemon..."
    cd "$WORKSPACE"

    if cortiva status &>/dev/null; then
        printf "\r  ${GREEN}✓${RESET} Fabric daemon already running          \n"
    else
        nohup cortiva start >> "$CORTIVA_HOME/logs/fabric.log" 2>&1 &
        FABRIC_PID=$!
        sleep 2

        if kill -0 "$FABRIC_PID" 2>/dev/null; then
            printf "\r  ${GREEN}✓${RESET} Fabric daemon started ${DIM}(PID $FABRIC_PID)${RESET}          \n"
        else
            printf "\r  ${YELLOW}○${RESET} Fabric daemon — check $CORTIVA_HOME/logs/fabric.log          \n"
        fi
    fi

    # Start the HQ node agent (connects to portal for remote management)
    printf "  ${YELLOW}◌${RESET} Connecting to Cortiva HQ..."
    > "$CORTIVA_HOME/logs/node.log"
    nohup cortiva-hq node connect --config "$CONFIG_FILE" \
        >> "$CORTIVA_HOME/logs/node.log" 2>&1 &
    NODE_PID=$!
    sleep 2

    if ! kill -0 "$NODE_PID" 2>/dev/null; then
        printf "\r  ${RED}✗${RESET} Node agent failed to start          \n"
        info "Check $CORTIVA_HOME/logs/node.log for details"
        report_progress "error" "Node agent failed to start"
    else
        # Wait for connection (up to 30s)
        CONNECTED=false
        for i in $(seq 1 30); do
            if grep -q "Node online" "$CORTIVA_HOME/logs/node.log" 2>/dev/null; then
                CONNECTED=true
                break
            fi
            if grep -q "Auth failed" "$CORTIVA_HOME/logs/node.log" 2>/dev/null; then
                break
            fi
            sleep 1
        done

        if [ "$CONNECTED" = true ]; then
            HOSTNAME_DETECTED=$(grep "Node online" "$CORTIVA_HOME/logs/node.log" | head -1 | sed 's/.*Node online: \([^ ]*\).*/\1/')
            printf "\r  ${GREEN}✓${RESET} Connected as ${BOLD}%s${RESET}          \n" "$HOSTNAME_DETECTED"
        else
            printf "\r  ${YELLOW}○${RESET} Node agent running — will connect in background          \n"
            info "Check $CORTIVA_HOME/logs/node.log for details"
        fi
    fi

    report_progress "done"
fi

# --- Register as system service ---

step "Registering system service"

PYTHON_PATH=$(which python3 2>/dev/null || echo "/usr/bin/python3")
CORTIVA_HQ_PATH=$(which cortiva-hq 2>/dev/null || echo "cortiva-hq")

if [ "$OS" = "Darwin" ]; then
    PLIST_PATH="$HOME/Library/LaunchAgents/com.cortiva.fabric.plist"
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] Would install launchd plist at $PLIST_PATH"
    else
        python3 -c "
from cortiva_hq_agent.launchd import install_plist
install_plist(
    config_path='$CONFIG_FILE',
    cortiva_hq_path='$CORTIVA_HQ_PATH',
    working_dir='$WORKSPACE',
    log_dir='$CORTIVA_HOME/logs',
)
" 2>/dev/null && ok "launchd service installed (started)" \
              || warn "launchd registration skipped — start manually with 'cortiva-hq node connect --config $CONFIG_FILE'"
    fi
elif [ "$OS" = "Linux" ]; then
    if [ "$DRY_RUN" = true ]; then
        info "[dry run] Would install systemd service"
    else
        python3 -c "
from cortiva_hq_agent.systemd import install_unit
install_unit(
    python_path='$PYTHON_PATH',
    working_dir='$WORKSPACE',
)
" 2>/dev/null && ok "systemd service installed (sudo systemctl enable cortiva)" \
              || warn "systemd registration skipped — start manually with 'cortiva start'"
    fi
fi

# --- Summary ---

printf "\n"
printf "  ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
if [ "$DRY_RUN" = true ]; then
    printf "\n  ${YELLOW}Dry run complete.${RESET} No changes were made.\n"
else
    if [ "${CONNECTED:-false}" = true ]; then
        printf "\n  ${GREEN}${BOLD}✓ Node is live${RESET}\n"
        printf "  ${DIM}Visible in the portal now. Agents will be deployed from HQ.${RESET}\n"
    else
        printf "\n  ${GREEN}${BOLD}✓ Cortiva installed${RESET}\n"
        printf "  ${DIM}Your node will appear in the portal once it connects.${RESET}\n"
    fi
    printf "\n"
    printf "  ${DIM}Workspace${RESET}  $WORKSPACE\n"
    printf "  ${DIM}Config${RESET}     $CONFIG_FILE\n"
    printf "  ${DIM}Logs${RESET}       $CORTIVA_HOME/logs/\n"
    printf "\n"
    printf "  ${DIM}Commands:${RESET}\n"
    printf "    cortiva status          ${DIM}Show agent status${RESET}\n"
    printf "    cortiva agent list      ${DIM}List deployed agents${RESET}\n"
    printf "    neo4j status            ${DIM}Check Neo4j status${RESET}\n"
    printf "    cortiva-hq license status  ${DIM}Check license${RESET}\n"
fi
printf "\n"
