#!/bin/bash
# install-searchai-macos.sh — one-liner installer for macOS (Apple Silicon).
#   curl -fsSL https://inference-server.searchblox.com/install-macos | sudo bash
# Config/UX parity with the Linux installer (install-searchai.sh): same
# properties keys, RAM-scaled admission floor and state pool, media model
# groups, generated API key, and an auto-start daemon (launchd for systemd).
# MODELS groups: 4b (default) 2b asr tts voice — e.g. MODELS='4b asr tts'.
# GPU/image add-ons and clustering are Linux-only for now.
set -euo pipefail

BASE="${SEARCHAI_BASE_URL:-https://inference-server.searchblox.com}"
MODELS="${MODELS:-4b}"
PORT="${SERVER_PORT:-8081}"
HOST="${SERVER_HOST:-127.0.0.1}"     # loopback default on personal machines; set 0.0.0.0 to serve the LAN
KV_CACHE="${KV_CACHE:-q8}"
MAX_INFLIGHT="${MAX_INFLIGHT:-4}"
MAX_CONTEXT="${MAX_CONTEXT:-8192}"
MAX_TOKENS="${MAX_TOKENS:-6144}"
MAX_BODY_MB="${MAX_BODY_MB:-64}"
PREFIX=/opt/searchai
CFG_DIR=/etc/searchai
DATA_DIR=/var/lib/searchai
PLIST=/Library/LaunchDaemons/com.searchblox.searchai.plist

[ "$(uname -s)" = Darwin ] || { echo "This installer is for macOS. Linux: $BASE/install"; exit 1; }
[ "$(uname -m)" = arm64 ] || { echo "Apple Silicon (arm64) required."; exit 1; }
[ "$(id -u)" = 0 ] || { echo "Run with sudo: curl -fsSL $BASE/install-macos | sudo bash"; exit 1; }

echo "==> SearchAI Inference Server — macOS installer"
mkdir -p "$PREFIX/bin" "$CFG_DIR" "$DATA_DIR/models"

echo "==> downloading binary (macos-arm64)"
curl -fsSL "$BASE/build/macos-arm64/searchai-server" -o "$PREFIX/bin/searchai-server"
curl -fsSL "$BASE/build/macos-arm64/searchai-server.sha256" -o /tmp/sai.sha256
(cd "$PREFIX/bin" && shasum -a 256 -c /tmp/sai.sha256 >/dev/null) || { echo "sha256 MISMATCH — aborting"; exit 1; }
chmod 755 "$PREFIX/bin/searchai-server"

echo "==> downloading model groups: $MODELS"
fetch() { [ -s "$DATA_DIR/models/$1" ] || curl -fL --retry 3 --progress-bar "$BASE/models/$1" -o "$DATA_DIR/models/$1"; }
DEFAULT_MODEL=""
case " $MODELS " in *" 4b "*) fetch q35-4b.gguf; fetch q35-4b-mmproj.gguf; DEFAULT_MODEL=q35-4b ;; esac
case " $MODELS " in *" 2b "*) fetch q35-2b.gguf; { fetch q35-2b-mmproj.gguf 2>/dev/null || true; }; DEFAULT_MODEL=${DEFAULT_MODEL:-q35-2b} ;; esac
case " $MODELS " in *" asr "*) fetch qwen3-asr-1.7b.gguf; fetch mmproj-qwen3-asr.gguf ;; esac
case " $MODELS " in *" tts "*) fetch qwen-talker-1.7b.gguf; fetch qwen-tts-codec.gguf ;; esac
case " $MODELS " in *" voice "*) mkdir -p "$DATA_DIR/models/tts-voices"; curl -fL --retry 3 -o "$DATA_DIR/models/tts-voices/samantha.wav" "$BASE/models/tts-voices/samantha.wav" 2>/dev/null || echo "  (no preset voices published — skipping)" ;; esac
[ -n "$DEFAULT_MODEL" ] || { echo "MODELS must include 4b or 2b"; exit 1; }
have() { [ -s "$DATA_DIR/models/$1" ]; }

RAM_MB=$(( $(sysctl -n hw.memsize) / 1048576 ))
MIN_FREE_MB=${MIN_FREE_MB:-$(( RAM_MB / 8 ))}; [ "$MIN_FREE_MB" -gt 4096 ] && MIN_FREE_MB=4096
MIN_RAM_GB=${MIN_RAM_GB:-$(( RAM_MB < 16384 ? 8 : 16 ))}

if [ ! -f "$CFG_DIR/server.properties" ]; then
  API_KEY=${API_KEY:-$(head -c 24 /dev/urandom | shasum -a 256 | cut -c1-48)}
  {
    echo "server.host=$HOST"
    echo "server.port=$PORT"
    echo "server.api-key=$API_KEY"
    echo
    echo "backend=cpu"
    echo "model=$DEFAULT_MODEL"
    echo "models-dir=$DATA_DIR/models"
    echo "kv-cache=$KV_CACHE"
    echo "max-context=$MAX_CONTEXT"
    echo "max-tokens=$MAX_TOKENS"
    echo "max-inflight=$MAX_INFLIGHT"
    echo "queue-timeout-ms=120000"
    echo "min-free-mb=$MIN_FREE_MB"
    echo "min-ram-gb=$MIN_RAM_GB"
    echo "max-loaded-models=0"
    echo "max-connections=512"
    echo "read-timeout-s=30"
    echo "max-body-mb=$MAX_BODY_MB"
    echo "tuning.batch=true"
    if have "${DEFAULT_MODEL}-mmproj.gguf"; then
      echo "enable-vision=true"
      echo "mmproj-path=$DATA_DIR/models/${DEFAULT_MODEL}-mmproj.gguf"
    else
      echo "enable-vision=false"
    fi
    if command -v ffmpeg >/dev/null 2>&1; then
      echo "video-ffmpeg=$(command -v ffmpeg)"; echo "video-fps=1"; echo "video-max-frames=16"
    fi
    have qwen3-asr-1.7b.gguf  && { echo "asr-model=qwen3-asr-1.7b"; echo "asr-mmproj=mmproj-qwen3-asr.gguf"; }
    have qwen-talker-1.7b.gguf && { echo "tts-talker=qwen-talker-1.7b.gguf"; echo "tts-codec=qwen-tts-codec.gguf"; }
  } > "$CFG_DIR/server.properties"
  chmod 0640 "$CFG_DIR/server.properties"
else
  API_KEY=$(sed -n 's/^server.api-key=//p' "$CFG_DIR/server.properties")
  echo "==> keeping existing $CFG_DIR/server.properties"
fi

# RAM-scaled idle state pool (parity with the Linux env file)
POOL_MB=""
if   [ "$RAM_MB" -le 34000 ]; then POOL_MB=1024
elif [ "$RAM_MB" -le 68000 ]; then POOL_MB=2048
fi

echo "==> installing launchd daemon"
launchctl bootout system "$PLIST" 2>/dev/null || true
cat > "$PLIST" <<EOF
<?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>com.searchblox.searchai</string>
  <key>ProgramArguments</key><array>
    <string>$PREFIX/bin/searchai-server</string>
    <string>$CFG_DIR/server.properties</string>
  </array>
  <key>EnvironmentVariables</key><dict>
    <key>SAI_FAST_PREFILL</key><string>1</string>
    <key>SAI_FAST_ATTN</key><string>1</string>
    <key>SAI_ROUTE_LOG</key><string>0</string>
$( [ -n "$POOL_MB" ] && printf '    <key>SAI_POOL_MAX_MB</key><string>%s</string>' "$POOL_MB" )
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
  <key>StandardOutPath</key><string>/var/log/searchai.log</string>
  <key>StandardErrorPath</key><string>/var/log/searchai.log</string>
</dict></plist>
EOF
launchctl bootstrap system "$PLIST"

echo "==> waiting for server + preloading $DEFAULT_MODEL"
for i in $(seq 1 90); do curl -fsS -m 2 "http://127.0.0.1:$PORT/health" >/dev/null 2>&1 && break; sleep 3; done
curl -fsS -m 5 "http://127.0.0.1:$PORT/health" >/dev/null || { echo "server did not come up — see /var/log/searchai.log"; exit 1; }
curl -fsS -m 600 -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d "{\"model\":\"$DEFAULT_MODEL\"}" "http://127.0.0.1:$PORT/v1/models/load" >/dev/null 2>&1 || true

cat <<EOF

==============================================================
  SearchAI Inference Server is running (starts on boot).
  API:      http://127.0.0.1:$PORT/v1   (OpenAI-compatible)
  Console:  http://127.0.0.1:$PORT/console
  API key (save it now):
      $API_KEY
  Config:   $CFG_DIR/server.properties
  Logs:     /var/log/searchai.log
  Stop:     sudo launchctl bootout system $PLIST
  Start:    sudo launchctl bootstrap system $PLIST
==============================================================
EOF
