#!/usr/bin/env bash
# GridShare Node Agent — macOS Installer
# Works on macOS 12+ (Monterey and later), Intel and Apple Silicon (M1/M2/M3/M4)
#
# One-liner install (no sudo required):
#   curl -fsSL https://gridshare.in/dl/install-mac.sh | bash
#
# Or download and run:
#   curl -fsSL -o install-mac.sh https://gridshare.in/dl/install-mac.sh
#   chmod +x install-mac.sh && bash install-mac.sh

set -euo pipefail

CENTRAL_URL="${GS_CENTRAL_URL:-https://relay.gridshare.in}"
# Account-first install: set GRIDSHARE_TOKEN (a gsp_… provision token minted in the
# portal) to install with ZERO prompts. Central reads name/UPI/city off the provider
# record saved at signup and hands back the node identity + long-lived API key.
# Priority: TOKEN > KEY > interactive prompts.
GRIDSHARE_TOKEN="${GRIDSHARE_TOKEN:-}"
# Fleet mode: set GRIDSHARE_KEY to skip registration and reuse an existing provider key.
# All machines sharing the same key earn under one account — perfect for gaming cafes.
GRIDSHARE_KEY="${GRIDSHARE_KEY:-}"
GRIDSHARE_LABEL="${GRIDSHARE_LABEL:-$(hostname -s)}"
GRIDSHARE_CITY="${GRIDSHARE_CITY:-}"
NODE_VERSION="1.3.64"
WHEEL_NAME="gridshare_node-${NODE_VERSION}-py3-none-any.whl"
INSTALL_DIR="$HOME/.gridshare-node"
CONFIG_DIR="$HOME/.gridshare"
CONFIG_FILE="$CONFIG_DIR/config.json"
LOG_FILE="$CONFIG_DIR/node.log"
VENV_DIR="$INSTALL_DIR/venv"
PLIST_LABEL="in.gridshare.node"
PLIST_FILE="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
SERVICE_NAME="$PLIST_LABEL"

# NOTE on `curl … | bash`: bash reads the SCRIPT from stdin, so we must NOT
# do `exec < /dev/tty` (that steals bash's script stream and hangs). Instead
# each interactive `read` below reads directly from /dev/tty, leaving the
# script pipe intact. A tiny helper guards the case where no TTY exists.
_TTY="/dev/tty"; [[ -r /dev/tty ]] || _TTY="/dev/null"

# ── Colours ───────────────────────────────────────────────────────────────────

_red()    { echo -e "\033[31m$*\033[0m"; }
_green()  { echo -e "\033[32m$*\033[0m"; }
_yellow() { echo -e "\033[33m$*\033[0m"; }
_cyan()   { echo -e "\033[36m$*\033[0m"; }
_gray()   { echo -e "\033[90m$*\033[0m"; }

step()  { echo; _cyan "── $*"; }
ok()    { _green "  [✓] $*"; }
info()  { _gray  "      $*"; }
warn()  { _yellow "  [!] $*"; }
fail()  { _red "  [✗] $*"; echo; exit 1; }

ask() {
    local prompt="$1" default="${2:-}"
    local answer
    if [[ -n "$default" ]]; then
        read -rp "      $prompt [$default]: " answer < "$_TTY"
        echo "${answer:-$default}"
    else
        read -rp "      $prompt: " answer < "$_TTY"
        echo "$answer"
    fi
}

# ── Header ────────────────────────────────────────────────────────────────────

clear
echo
_cyan "  ╔══════════════════════════════════════════════════════════════╗"
_cyan "  ║        GridShare — GPU Node Agent Installer (macOS)         ║"
_cyan "  ║        Earn INR from your idle Mac  •  gridshare.in         ║"
_cyan "  ╚══════════════════════════════════════════════════════════════╝"
echo
echo "  Welcome! This installer registers your Mac with India's compute"
echo "  marketplace so you can earn INR while it's idle."
echo
echo "  GridShare automatically pauses when you use your Mac — no impact"
echo "  on your experience. Resumes earning when you walk away."
echo
_gray "  Relay server : $CENTRAL_URL"
echo

# ── macOS version check ───────────────────────────────────────────────────────

step "Checking macOS"
MAC_VER=$(sw_vers -productVersion 2>/dev/null || echo "0.0")
MAC_MAJOR=$(echo "$MAC_VER" | cut -d. -f1)
if (( MAC_MAJOR < 12 )); then
    warn "macOS $MAC_VER detected — GridShare recommends macOS 12+ (Monterey or later)"
    warn "Some features may not work on older versions."
else
    ok "macOS $MAC_VER"
fi

# ── Chip detection ────────────────────────────────────────────────────────────

step "Detecting GPU / Chip"

CHIP_TYPE="intel"
GPU_NAME="Intel GPU"
GPU_VRAM_GB="0"
HAS_APPLE_SILICON=false

ARCH=$(uname -m)
if [[ "$ARCH" == "arm64" ]]; then
    HAS_APPLE_SILICON=true
    CHIP_TYPE="apple_silicon"
    # Get chip name from system_profiler
    CHIP_NAME=$(system_profiler SPHardwareDataType 2>/dev/null | \
                awk '/Chip/{print substr($0, index($0,$3))}' | head -1 | xargs || echo "Apple Silicon")
    # Get GPU core count
    GPU_CORES=$(system_profiler SPDisplaysDataType 2>/dev/null | \
                grep "Total Number of Cores" | head -1 | awk '{print $NF}' || echo "")
    GPU_NAME="$CHIP_NAME GPU"
    [[ -n "$GPU_CORES" ]] && GPU_NAME="$CHIP_NAME (${GPU_CORES}-core GPU)"
    # Unified memory
    UNIFIED_MEM=$(system_profiler SPHardwareDataType 2>/dev/null | \
                  awk '/Memory:/{print $2" "$3}' | head -1 || echo "")
    ok "Apple Silicon: $CHIP_NAME"
    [[ -n "$UNIFIED_MEM" ]] && ok "Unified Memory: $UNIFIED_MEM (shared GPU/CPU)"
    [[ -n "$GPU_CORES" ]] && ok "GPU Cores: $GPU_CORES"
    warn "Note: Apple Silicon GPU renting is in beta — Metal compute supported"
    warn "ML buyers use PyTorch MPS or JAX Metal backends"
else
    # Intel Mac — check for discrete GPU
    GPU_INFO=$(system_profiler SPDisplaysDataType 2>/dev/null | \
               grep -E "Chipset Model|VRAM" | head -4 || echo "")
    GPU_NAME=$(echo "$GPU_INFO" | grep "Chipset Model" | head -1 | \
               sed 's/.*Chipset Model: //' | xargs || echo "Intel Integrated GPU")
    VRAM_LINE=$(echo "$GPU_INFO" | grep "VRAM" | head -1 | \
                sed 's/.*VRAM.*: //' | xargs || echo "")
    ok "Intel Mac detected"
    ok "GPU: $GPU_NAME"
    [[ -n "$VRAM_LINE" ]] && ok "VRAM: $VRAM_LINE"
    warn "Intel Macs have lower ML earnings — Apple Silicon (M1/M2/M3/M4) is preferred"
fi

RAM_GB=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1024 / 1024 / 1024 ))
CPU_MODEL=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo "Apple Processor")
ok "CPU: $CPU_MODEL"
ok "RAM: ${RAM_GB} GB"

echo
read -rp "      Continue with this hardware? [Y/n]: " cont < "$_TTY"
[[ "${cont:-y}" =~ ^[Yy] ]] || exit 0

# ── Python check ──────────────────────────────────────────────────────────────

step "Checking Python 3.10+"

PYTHON_BIN=""
for candidate in python3.12 python3.11 python3.10 python3; do
    if command -v "$candidate" &>/dev/null; then
        ver=$("$candidate" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "")
        if [[ "$ver" =~ ^3\.(1[0-9]|[2-9][0-9])$ ]]; then
            PYTHON_BIN=$(command -v "$candidate")
            ok "Python $ver at $PYTHON_BIN"
            break
        fi
    fi
done

if [[ -z "$PYTHON_BIN" ]]; then
    # No system Python 3.10+ — fetch a PRIVATE, relocatable one just for the
    # agent. No sudo, no Homebrew, does NOT touch the system Python. This is the
    # same kind of standalone CPython build that tools like `uv` use, so a
    # provider never has to install Python by hand. ~25 MB, one-time.
    warn "No Python 3.10+ found — installing a private copy for GridShare (no system change)…"

    arch=$(uname -m)
    case "$arch" in
        arm64)  PBS_ARCH="aarch64-apple-darwin" ;;
        x86_64) PBS_ARCH="x86_64-apple-darwin" ;;
        *)      fail "Unsupported macOS architecture: $arch" ;;
    esac
    PBS_TAG="20241016"; PBS_PYVER="3.12.7"
    PBS_URL="https://github.com/astral-sh/python-build-standalone/releases/download/${PBS_TAG}/cpython-${PBS_PYVER}+${PBS_TAG}-${PBS_ARCH}-install_only.tar.gz"

    mkdir -p "$INSTALL_DIR"
    if curl -fsSL -o "$INSTALL_DIR/python.tar.gz" "$PBS_URL"; then
        tar -xzf "$INSTALL_DIR/python.tar.gz" -C "$INSTALL_DIR"   # → $INSTALL_DIR/python/
        rm -f "$INSTALL_DIR/python.tar.gz"
        if [[ -x "$INSTALL_DIR/python/bin/python3" ]]; then
            PYTHON_BIN="$INSTALL_DIR/python/bin/python3"
            ok "Bundled Python ready: $("$PYTHON_BIN" --version 2>&1)"
        fi
    fi

    # Fallback to Homebrew only if the bundled download failed (offline, etc.)
    if [[ -z "$PYTHON_BIN" ]] && command -v brew &>/dev/null; then
        warn "Bundled download failed — falling back to Homebrew…"
        brew install python@3.11 && PYTHON_BIN="$(brew --prefix python@3.11)/bin/python3.11"
    fi

    [[ -z "$PYTHON_BIN" ]] && fail "Could not obtain Python 3.10+ (check your internet connection and re-run)."
fi

# ── Create directories ─────────────────────────────────────────────────────────

step "Creating directories"
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$HOME/Library/LaunchAgents"
ok "Install : $INSTALL_DIR"
ok "Config  : $CONFIG_DIR"
ok "Service : $HOME/Library/LaunchAgents"

# ── Virtual environment + wheel ───────────────────────────────────────────────

step "Installing GridShare Node Agent"

if [[ ! -f "$VENV_DIR/bin/python" ]]; then
    info "Creating virtual environment ..."
    "$PYTHON_BIN" -m venv "$VENV_DIR"
fi

PIP="$VENV_DIR/bin/pip"
NODE_BIN="$VENV_DIR/bin/gridshare-node"

info "Upgrading pip ..."
"$PIP" install --upgrade pip --quiet 2>&1 | tail -1 || true

WHEEL_PATH="/tmp/$WHEEL_NAME"
info "Downloading GridShare Node Agent ..."
if curl -fsSL -o "$WHEEL_PATH" "$CENTRAL_URL/dl/$WHEEL_NAME" 2>/dev/null; then
    ok "Downloaded $WHEEL_NAME"
    "$PIP" install "$WHEEL_PATH" --quiet 2>&1 | tail -1 || true
    "$PIP" install "bcrypt>=4.0,<5.0" --quiet 2>&1 | tail -1 || true
    rm -f "$WHEEL_PATH"
else
    warn "Could not download from server — trying PyPI ..."
    "$PIP" install gridshare-node --quiet 2>&1 | tail -1 || true
fi

[[ -f "$NODE_BIN" ]] || fail "gridshare-node not found after install. Re-run the installer."
ok "GridShare Node Agent installed"

# ── ML Frameworks (PyTorch MPS + MLX + HuggingFace) ──────────────────────────

step "Installing ML Frameworks"
info "This enables GPU-accelerated ML workloads on your Mac (training, inference, diffusion)."

if [[ "$HAS_APPLE_SILICON" == true ]]; then
    info "Installing PyTorch with MPS (Metal Performance Shaders) backend..."
    "$PIP" install --upgrade torch torchvision torchaudio --quiet 2>&1 | tail -2 || \
        warn "PyTorch install failed — retry: $PIP install torch torchvision torchaudio"

    MPS_OK=$("$VENV_DIR/bin/python" -c \
        "import torch; print('ok' if torch.backends.mps.is_available() else 'no')" 2>/dev/null || echo "no")
    if [[ "$MPS_OK" == "ok" ]]; then
        ok "PyTorch MPS backend active ✓"
    else
        warn "MPS check returned no — may need macOS 12.3+. CPU fallback enabled."
    fi

    # MLX requires macOS 13.5+ and Apple Silicon
    if (( MAC_MAJOR >= 13 )); then
        info "Installing MLX (Apple's native ML framework — faster than MPS for LLMs)..."
        "$PIP" install mlx mlx-lm --quiet 2>&1 | tail -2 || warn "MLX install failed — non-critical"
        MLX_OK=$("$VENV_DIR/bin/python" -c "import mlx.core; print('ok')" 2>/dev/null || echo "no")
        [[ "$MLX_OK" == "ok" ]] && ok "MLX installed ✓" || \
            warn "MLX unavailable (needs macOS 13.5+, you have $MAC_VER)"
    else
        info "Skipping MLX — requires macOS 13+ (you have $MAC_VER). MPS-only mode."
    fi

    info "Installing HuggingFace stack (Transformers, Diffusers, Accelerate)..."
    "$PIP" install transformers accelerate diffusers safetensors sentencepiece --quiet \
        2>&1 | tail -2 || warn "HuggingFace stack failed — retry manually"
    ok "HuggingFace Transformers + Diffusers installed ✓"

    info "Installing llama-cpp-python with Metal support (for GGUF model serving)..."
    CMAKE_ARGS="-DGGML_METAL=on" \
    "$PIP" install llama-cpp-python --no-binary llama-cpp-python --quiet 2>&1 | tail -2 || \
        warn "llama-cpp-python failed — non-critical (optional GGUF serving)"

    ok "ML stack ready: PyTorch MPS · MLX · HuggingFace · llama.cpp Metal"
    info "Buyers can now run: LLM inference (Llama/Mistral/Phi), Stable Diffusion, LoRA fine-tuning"
else
    # Intel Mac — CPU PyTorch only
    info "Intel Mac: installing CPU-only PyTorch..."
    "$PIP" install --upgrade torch torchvision torchaudio --quiet 2>&1 | tail -2 || \
        warn "PyTorch install failed"
    "$PIP" install transformers accelerate --quiet 2>&1 | tail -2 || true
    ok "PyTorch CPU + HuggingFace Transformers installed"
fi

# ── Registration ──────────────────────────────────────────────────────────────

step "Registering with GridShare"

SKIP_REG=false
if [[ -f "$CONFIG_FILE" ]]; then
    EXISTING_KEY=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('api_key',''))" 2>/dev/null || echo "")
    EXISTING_NODE=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('node_id',''))" 2>/dev/null || echo "")
    if [[ -n "$EXISTING_KEY" && -n "$EXISTING_NODE" ]]; then
        ok "Already registered: node $EXISTING_NODE"
        read -rp "      Re-register with new details? [y/N]: " redo < "$_TTY"
        [[ "${redo:-n}" =~ ^[Yy] ]] || SKIP_REG=true
    fi
fi

SCHEDULE_ENABLED=false
SCHEDULE_START="22:00"
SCHEDULE_END="08:00"
OWNER_NAME=""
UPI_ID=""
CITY="$GRIDSHARE_CITY"
EMAIL_OPT=""
MACHINE_LABEL="$GRIDSHARE_LABEL"
API_KEY=""
NODE_ID=""
PROV_ID=""

# Auto-assign price based on chip tier (runs in both normal and fleet mode)
PRICE_PER_HOUR=8
TIER_NAME="Apple Silicon"
CHIP_LOWER_PRICING=$(echo "${CHIP_NAME:-}" | tr '[:upper:]' '[:lower:]')
if [[ "$HAS_APPLE_SILICON" == true ]]; then
    # Generation-aware pricing — matches gridshare.in/provider calculator exactly
    if   echo "$CHIP_LOWER_PRICING" | grep -q "m4 ultra"; then PRICE_PER_HOUR=46; TIER_NAME="Apple M4 Ultra"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m3 ultra"; then PRICE_PER_HOUR=42; TIER_NAME="Apple M3 Ultra"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m2 ultra"; then PRICE_PER_HOUR=38; TIER_NAME="Apple M2 Ultra"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m1 ultra"; then PRICE_PER_HOUR=34; TIER_NAME="Apple M1 Ultra"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m4 max";   then PRICE_PER_HOUR=36; TIER_NAME="Apple M4 Max"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m3 max";   then PRICE_PER_HOUR=34; TIER_NAME="Apple M3 Max"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m2 max";   then PRICE_PER_HOUR=32; TIER_NAME="Apple M2 Max"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m1 max";   then PRICE_PER_HOUR=31; TIER_NAME="Apple M1 Max"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m4 pro";   then PRICE_PER_HOUR=33; TIER_NAME="Apple M4 Pro"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m3 pro";   then PRICE_PER_HOUR=30; TIER_NAME="Apple M3 Pro"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m2 pro";   then PRICE_PER_HOUR=24; TIER_NAME="Apple M2 Pro"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m1 pro";   then PRICE_PER_HOUR=18; TIER_NAME="Apple M1 Pro"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m4";       then PRICE_PER_HOUR=18; TIER_NAME="Apple M4"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m3";       then PRICE_PER_HOUR=16; TIER_NAME="Apple M3"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m2";       then PRICE_PER_HOUR=14; TIER_NAME="Apple M2"
    elif echo "$CHIP_LOWER_PRICING" | grep -q "m1";       then PRICE_PER_HOUR=13;  TIER_NAME="Apple M1"
    fi
else
    # Intel Mac fallback — minimal earnings, CPU workloads only
    PRICE_PER_HOUR=6; TIER_NAME="Intel Mac (CPU)"
fi
NET_EARNINGS=$(echo "scale=2; $PRICE_PER_HOUR * 0.7" | bc 2>/dev/null || echo "?")
ok "Tier: $TIER_NAME · ₹${PRICE_PER_HOUR}/hr · You earn ₹${NET_EARNINGS}/hr (70% share)"

if [[ "$SKIP_REG" == false ]]; then

  if [[ -n "$GRIDSHARE_TOKEN" ]]; then
    # ── Account-first mode: single tokenized exchange, ZERO prompts ───────────
    # The provider already entered name/UPI/city at signup; central reads them
    # off the provider record. We skip the entire questionnaire (name/UPI/city/
    # price/email/schedule) and make ONE call to /nodes/provision. A stale token
    # sends the provider back to the portal — we do NOT fall back to prompting.
    echo
    echo "  ── Account-First Install ───────────────────────────────────────────"
    _gray "  Provision token found — no questions asked."
    _gray "  Your saved name / UPI / city come straight from your dashboard."
    echo

    LOCAL_ID="node_$(python3 -c "import uuid; print(uuid.uuid4().hex[:16])")"

    GPU_JSON="[]"
    if [[ "$HAS_APPLE_SILICON" == true ]]; then
        GPU_JSON="[{\"name\":\"$GPU_NAME\",\"vram_gb\":$RAM_GB,\"vendor\":\"apple\"}]"
    fi
    HW_JSON="{\"cpu_model\":\"$CPU_MODEL\",\"ram_gb\":$RAM_GB,\"gpu_count\":1,\"gpus\":$GPU_JSON,\"platform\":\"darwin\",\"arch\":\"$ARCH\"}"

    info "Provisioning with GridShare ..."
    PROV_HTTP=$(curl -fsSL -w "\n%{http_code}" -X POST "$CENTRAL_URL/nodes/provision" \
        -H "Content-Type: application/json" \
        -d "{\"token\":\"$GRIDSHARE_TOKEN\",\"local_id\":\"$LOCAL_ID\",\"machine_label\":\"$MACHINE_LABEL\",\"hardware\":$HW_JSON,\"version\":\"$NODE_VERSION\"}" \
        2>/dev/null || echo $'\n000')
    PROV_CODE=$(echo "$PROV_HTTP" | tail -1)
    PROV_RESP=$(echo "$PROV_HTTP" | sed '$d')

    case "$PROV_CODE" in
        200|201) : ;;
        401) fail "That install link is invalid. Copy a fresh one from your dashboard." ;;
        409) fail "This install link was already used. Mint a new one per machine." ;;
        410) fail "This install link expired. Generate a new one from your dashboard." ;;
        000) fail "Could not reach GridShare server. Check your connection and re-run." ;;
        *)   fail "Provisioning failed (HTTP $PROV_CODE). Mint a fresh install link and re-run." ;;
    esac

    # Map the /provision response into the same vars the config-write block uses.
    NODE_ID=$(echo "$PROV_RESP"   | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('node_id',''))" 2>/dev/null || echo "")
    API_KEY=$(echo "$PROV_RESP"   | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('api_key',''))" 2>/dev/null || echo "")
    PROV_ID=$(echo "$PROV_RESP"   | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('provider_id',''))" 2>/dev/null || echo "")
    OWNER_NAME=$(echo "$PROV_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('owner_name',''))" 2>/dev/null || echo "")
    UPI_ID=$(echo "$PROV_RESP"    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('upi_id',''))" 2>/dev/null || echo "")
    CITY=$(echo "$PROV_RESP"      | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('city',''))" 2>/dev/null || echo "")
    SRV_PRICE=$(echo "$PROV_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('price_per_hour_inr',''))" 2>/dev/null || echo "")
    [[ -n "$SRV_PRICE" ]] && PRICE_PER_HOUR="$SRV_PRICE"

    [[ -n "$API_KEY" && -n "$NODE_ID" ]] || \
        fail "Provisioning response was incomplete. Mint a fresh install link and re-run."

    ok "Node registered: $NODE_ID"
    ok "Installed for ${OWNER_NAME:-your account} · payouts to ${UPI_ID:-UPI on file} · ₹${PRICE_PER_HOUR}/hr"

  elif [[ -n "$GRIDSHARE_KEY" ]]; then
    # ── Fleet mode: reuse existing provider API key ───────────────────────────
    echo
    echo "  ── Fleet Mode Detected ─────────────────────────────────────────────"
    _gray "  GRIDSHARE_KEY found — skipping registration questionnaire."
    _gray "  This Mac will appear as a new node under your existing provider account."
    echo
    API_KEY="$GRIDSHARE_KEY"
    ok "Fleet mode: API key loaded"
    ok "Machine label: $MACHINE_LABEL"

  else
    # ── Normal mode: full registration questionnaire ──────────────────────────
    echo
    echo "  We need a few details to set up your earnings account."
    _gray "  These are used for UPI payouts only — never shared publicly."
    echo

    while [[ -z "$OWNER_NAME" ]]; do
        OWNER_NAME=$(ask "Your full name (for UPI payouts)")
        [[ -z "$OWNER_NAME" ]] && warn "Name is required."
    done

    while [[ "$UPI_ID" != *@* ]]; do
        UPI_ID=$(ask "UPI ID  (e.g. yourname@upi, mobile@ybl, name@paytm)")
        [[ "$UPI_ID" != *@* ]] && warn "Invalid UPI ID — must contain @"
    done

    echo
    echo "      Select your city:"
    CITIES=("Hyderabad" "Bangalore" "Mumbai" "Delhi" "Chennai" "Pune" "Kolkata" "Ahmedabad" "Jaipur" "Other")
    for i in "${!CITIES[@]}"; do
        _gray "        $((i+1)). ${CITIES[$i]}"
    done
    CITY_NUM=$(ask "City number (1-${#CITIES[@]})" "1")
    if [[ "$CITY_NUM" =~ ^[0-9]+$ ]] && (( CITY_NUM >= 1 && CITY_NUM <= ${#CITIES[@]} )); then
        CITY="${CITIES[$((CITY_NUM-1))]}"
    else
        CITY="$CITY_NUM"
    fi
    [[ "$CITY" == "Other" ]] && CITY=$(ask "Enter your city name")

    EMAIL_OPT=$(ask "Email (optional, for account recovery)" "")

    echo
    echo "  ── Availability Schedule (Optional) ─────────────────────────────"
    _gray "  Set hours when your Mac is available for rent."
    _gray "  Example: 22:00-08:00 = earn overnight while Mac charges."
    _gray "  Press Enter to skip and earn 24/7."
    echo
    SCHED_RAW=$(ask "Set availability schedule? [y/N]" "N")
    if [[ "${SCHED_RAW:-n}" =~ ^[Yy] ]]; then
        SCHEDULE_ENABLED=true
        # Validate HH:MM — a stray 'n' here used to save a broken schedule
        # that silently blocked all dispatches (found 2026-06-11).
        ask_time() {
            local _prompt="$1" _default="$2" _val
            while true; do
                _val=$(ask "$_prompt" "$_default")
                if [[ "$_val" =~ ^([01]?[0-9]|2[0-3]):[0-5][0-9]$ ]]; then
                    echo "$_val"; return
                fi
                warn "  '$_val' is not a valid time — use 24h HH:MM (e.g. 22:00)" >&2
            done
        }
        SCHEDULE_START=$(ask_time "Available from (HH:MM)" "22:00")
        SCHEDULE_END=$(ask_time "Available until (HH:MM)" "08:00")
        ok "Schedule: ${SCHEDULE_START}–${SCHEDULE_END} daily"
    else
        info "No schedule — earning 24/7 (auto-pause kicks in when you use the Mac)"
    fi

    echo
    info "Registering with GridShare ..."

    REG_RESP=$(curl -fsSL -X POST "$CENTRAL_URL/nodes/issue-key" \
        -H "Content-Type: application/json" \
        -d "{\"owner_name\":\"$OWNER_NAME\",\"upi_id\":\"$UPI_ID\",\"email\":\"$EMAIL_OPT\"}" \
        2>/dev/null || echo "")

    if [[ -n "$REG_RESP" ]]; then
        API_KEY=$(echo "$REG_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('api_key',''))" 2>/dev/null || echo "")
        PROV_ID=$(echo "$REG_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('provider_id',''))" 2>/dev/null || echo "")
        [[ -n "$API_KEY" ]] && ok "Account created. Provider ID: $PROV_ID" || \
            warn "Could not parse response — will register on first connection."
    else
        warn "Could not reach GridShare server. Config saved — will register when connected."
    fi
  fi

    # ── Fleet or normal: generate local_id and register hardware ─────────────
    # (Skipped on the token path — /nodes/provision already registered the node
    #  and set LOCAL_ID / NODE_ID / API_KEY above.)
    if [[ -z "$GRIDSHARE_TOKEN" ]]; then
    if [[ -n "$GRIDSHARE_KEY" ]]; then
        _LABEL_SLUG=$(echo "$MACHINE_LABEL" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '_' | sed 's/_*$//')
        LOCAL_ID="fleet_${_LABEL_SLUG}_$(python3 -c "import uuid; print(uuid.uuid4().hex[:8])")"
    else
        LOCAL_ID="node_$(python3 -c "import uuid; print(uuid.uuid4().hex[:16])")"
    fi

    if [[ -n "$API_KEY" ]]; then
        GPU_JSON="[]"
        if [[ "$HAS_APPLE_SILICON" == true ]]; then
            GPU_JSON="[{\"name\":\"$GPU_NAME\",\"vram_gb\":$RAM_GB,\"vendor\":\"apple\"}]"
        fi
        HW_JSON="{\"cpu_model\":\"$CPU_MODEL\",\"ram_gb\":$RAM_GB,\"gpu_count\":1,\"gpus\":$GPU_JSON,\"platform\":\"darwin\",\"arch\":\"$ARCH\"}"
        NODE_RESP=$(curl -fsSL -X POST "$CENTRAL_URL/nodes/register" \
            -H "Content-Type: application/json" \
            -d "{\"api_key\":\"$API_KEY\",\"upi_id\":\"$UPI_ID\",\"owner_name\":\"$OWNER_NAME\",\"city\":\"$CITY\",\"local_id\":\"$LOCAL_ID\",\"machine_label\":\"$MACHINE_LABEL\",\"hardware\":$HW_JSON,\"version\":\"$NODE_VERSION\"}" \
            2>/dev/null || echo "")
        if [[ -n "$NODE_RESP" ]]; then
            NODE_ID=$(echo "$NODE_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('node_id',''))" 2>/dev/null || echo "")
            [[ -n "$NODE_ID" ]] && ok "Node registered: $NODE_ID" || \
                warn "Node registration pending — will complete on first connection."
        fi
    fi
    fi   # end: skip register on token path

    python3 - <<PYEOF
import json
cfg = {
    "node_id": """$NODE_ID""" or None,
    "api_key": """$API_KEY""" or None,
    "upi_id": """$UPI_ID""",
    "owner_name": """$OWNER_NAME""",
    "city": """$CITY""",
    "local_id": """$LOCAL_ID""",
    "machine_label": """$MACHINE_LABEL""",
    "max_gpu_temp_c": 90,      # macOS thermal throttles earlier — use higher limit
    "max_cpu_temp_c": 95,
    "max_vrm_temp_c": 105,
    "max_psu_pct": 85,
    "min_fan_rpm": 0,          # Apple Silicon: passive cooling, no fans detectable
    "quiet_hours_start": 22,
    "quiet_hours_end": 7,
    "quiet_hours_enabled": True,
    "quiet_gpu_tdp_pct": 60,
    "available": True,
    "allow_spot": True,
    "allow_ondemand": True,
    "max_jobs_concurrent": 1,
    "bandwidth_limit_mbps": 0,
    "accept_job_types": ["training","inference","batch"],
    "safety_auto_cutoff": True,
    "sustained_load_cutoff_hours": 4,
    "sustained_cooldown_minutes": 10,
    # macOS: always a desktop/laptop — auto-pause via ioreg HIDIdleTime
    "auto_pause_on_user_activity": True,
    "schedule_enabled": """$SCHEDULE_ENABLED""" == "true",
    "schedule_start": """$SCHEDULE_START""",
    "schedule_end": """$SCHEDULE_END""",
    "schedule_timezone": "Asia/Kolkata",
    "schedule_days": ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
    "price_per_hour_inr": float("""$PRICE_PER_HOUR"""),
    "dashboard_port": 7842,
    "dashboard_open_on_start": True,
}
cfg["node_id"] = cfg["node_id"] if cfg["node_id"] else None
cfg["api_key"] = cfg["api_key"] if cfg["api_key"] else None
with open("$CONFIG_FILE", "w") as f:
    json.dump(cfg, f, indent=2)
PYEOF
    ok "Config saved: $CONFIG_FILE"
fi

# ── launchd service (runs on login, no root required) ─────────────────────────

step "Installing launchd service"

cat > "$PLIST_FILE" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>${PLIST_LABEL}</string>

  <key>ProgramArguments</key>
  <array>
    <string>${NODE_BIN}</string>
    <string>start</string>
  </array>

  <key>WorkingDirectory</key>
  <string>${CONFIG_DIR}</string>

  <key>EnvironmentVariables</key>
  <dict>
    <key>GRIDSHARE_CONFIG_DIR</key>
    <string>${CONFIG_DIR}</string>
    <key>PYTHONUNBUFFERED</key>
    <string>1</string>
  </dict>

  <key>RunAtLoad</key>
  <true/>

  <key>KeepAlive</key>
  <true/>

  <key>StandardOutPath</key>
  <string>${LOG_FILE}</string>

  <key>StandardErrorPath</key>
  <string>${CONFIG_DIR}/node-error.log</string>

  <key>ThrottleInterval</key>
  <integer>10</integer>
</dict>
</plist>
PLIST

ok "launchd plist: $PLIST_FILE"

# Load the service
launchctl unload "$PLIST_FILE" 2>/dev/null || true

echo
read -rp "      Start GridShare Node Agent now? [Y/n]: " start_now < "$_TTY"
if [[ "${start_now:-y}" =~ ^[Yy] ]]; then
    launchctl load "$PLIST_FILE"
    sleep 3
    if launchctl list | grep -q "$PLIST_LABEL"; then
        ok "Service started and running!"
    else
        warn "Service may still be starting — check: launchctl list | grep gridshare"
    fi
    # ── Verify the node is actually LIVE (connected to the relay) ─────────────
    # "Started" ≠ "online": a firewall / blocked outbound HTTPS or a slow first
    # connect can leave the agent running but never visible in the marketplace,
    # so the provider assumes they're earning and silently isn't. Confirm the
    # real WS connection via the local agent's /api/status before declaring done.
    echo ""
    echo "  Verifying your node is LIVE ..."
    NODE_ONLINE=false
    for _i in $(seq 1 20); do   # poll ~40s
      _conn=$(curl -s --max-time 3 http://127.0.0.1:7842/api/status 2>/dev/null \
              | python3 -c "import sys,json; print(json.load(sys.stdin).get('connection',{}).get('connected', False))" 2>/dev/null || echo "")
      if [ "$_conn" = "True" ]; then NODE_ONLINE=true; break; fi
      sleep 2
    done
    if [ "$NODE_ONLINE" = "true" ]; then
      ok "✅ LIVE — your Mac is connected and now listed in the marketplace."
    else
      warn "Agent is running but hasn't connected yet (firewall / blocked outbound HTTPS / slow first connect)."
      warn "The dashboard at http://localhost:7842 shows live status; give it a minute, then confirm at https://gridshare.in/provider"
    fi
fi

# ── Done ─────────────────────────────────────────────────────────────────────

echo
echo
_green "  ╔══════════════════════════════════════════════════════════════╗"
_green "  ║   ✅  GridShare Node Agent Installed Successfully!           ║"
_green "  ╚══════════════════════════════════════════════════════════════╝"
echo

if [[ -f "$CONFIG_FILE" ]]; then
    DISP_NAME=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('owner_name',''))" 2>/dev/null || echo "")
    DISP_UPI=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('upi_id',''))" 2>/dev/null || echo "")
    DISP_NODE=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('node_id',''))" 2>/dev/null || echo "")
    DISP_KEY=$(python3 -c "import json; d=json.load(open('$CONFIG_FILE')); print(d.get('api_key') or '')" 2>/dev/null || echo "")
    [[ -n "$DISP_NAME" ]] && echo "  Provider : $DISP_NAME"
    [[ -n "$DISP_UPI" ]]  && echo "  UPI ID   : $DISP_UPI"
    [[ -n "$DISP_NODE" ]] && echo "  Node ID  : $DISP_NODE"
    if [[ -n "$DISP_KEY" ]]; then
        echo "  API Key  : $DISP_KEY"
        _gray "             ↳ keep this safe — it's your login to the cloud portal"
    fi
    echo
fi

echo "  Local dashboard : http://localhost:7842 (this machine)"
echo "  Cloud portal    : https://gridshare.in/provider  (log in with the API Key above to track earnings across all your PCs)"
echo "  Logs      : $LOG_FILE"
echo "  Config    : $CONFIG_FILE"
echo
echo "  Service commands:"
echo "    Stop    : launchctl unload '$PLIST_FILE'"
echo "    Start   : launchctl load '$PLIST_FILE'"
echo "    Status  : launchctl list | grep gridshare"
echo "    Logs    : tail -f '$LOG_FILE'"
echo "    Uninstall: launchctl unload '$PLIST_FILE' && rm '$PLIST_FILE'"
echo
_gray "  Auto-pause: GridShare detects when you use your Mac via ioreg."
_gray "  Billing pauses while you work/browse. Resumes when Mac is idle."
echo
_gray "  Support: support@gridshare.in  |  https://gridshare.in"
echo

# Open dashboard in browser
open "http://localhost:7842" 2>/dev/null || true
