#!/bin/sh
# Clawdie-AI Firstboot Orchestrator
# Runs once on first boot (rc.d/clawdie-firstboot, REQUIRE: NETWORKING LOGIN)
# Dispatches to clawdie-shell-*.sh modules for cloud or baremetal path
#
# Usage:
#   firstboot.sh              — Normal run (wizard on baremetal, pre-baked on cloud)
#   firstboot.sh --resume     — Skip already-completed steps, continue from last failure
#   firstboot.sh --reset      — Clear progress and start over from the beginning

set -eu

SHARE="${SHARE:-/usr/local/share/clawdie-iso}"
LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}"
PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}"
RC_CONF="${RC_CONF:-/etc/rc.conf}"

# ── Arg parsing ────────────────────────────────────────────────────────────
RESUME=0
case "${1:-}" in
  --resume) RESUME=1 ;;
  --reset)
    rm -f "$PROGRESS_FILE"
    echo "$(date '+%H:%M:%S') [firstboot] Progress reset — starting over" | tee -a "$LOG_FILE"
    ;;
  --help|-h)
    echo "Usage: firstboot.sh [--resume|--reset]"
    echo "  --resume  Skip completed steps, continue from last failure"
    echo "  --reset   Clear progress file and start from the beginning"
    exit 0
    ;;
esac

log_msg() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG_FILE"; }

# ── Checkpoint helpers ─────────────────────────────────────────────────────
# Mark a step done in the progress file
step_done() {
  echo "$1" >> "$PROGRESS_FILE"
}

# Return 0 (true) if the step was already completed
step_completed() {
  [ "$RESUME" -eq 1 ] && grep -qx "$1" "$PROGRESS_FILE" 2>/dev/null
}

# Run a module function with checkpoint guard.
# Usage: run_step <step_name> <function> [description] [step_num]
run_step() {
  _step="$1"
  _fn="$2"
  _desc="${3:-$_fn}"
  _step_num="${4:-0}"  # Optional: step number for progress tracking

  if step_completed "$_step"; then
    log_msg "[firstboot] Skipping $_step (already completed)"
    [ "$_step_num" -gt 0 ] && echo "PROGRESS=$_step_num" >> "$PROGRESS_FILE"
    return 0
  fi

  log_msg "[firstboot] Running: $_desc"
  "$_fn"
  step_done "$_step"
  [ "$_step_num" -gt 0 ] && echo "PROGRESS=$_step_num" >> "$PROGRESS_FILE"
}

# Run a step only when the current boot mode is in the allowed list.
# Usage: run_step_if "<modes>" <step_name> <function> [description] [step_num]
# Modes: space-separated list of CLAWDIE_BOOT_MODE values (install upgrade repair)
# Steps not applicable to the current mode are marked done (so --resume works).
run_step_if() {
  _allowed="$1"; shift
  _step="$1"
  _mode="${CLAWDIE_BOOT_MODE:-install}"
  if echo "$_allowed" | grep -qw "$_mode"; then
    run_step "$@"
  else
    if ! step_completed "$_step"; then
      log_msg "[firstboot] Skipping $_step (not applicable in $_mode mode)"
      step_done "$_step"
    fi
  fi
}

# ── Set package path to bundled packages on HDD ──────────────────────────────
# After bsdinstall, packages live at SHARE/packages (not /mnt/media)
export USB_PKG_PATH="${SHARE}/packages"

# ── Prevent modules from auto-running when sourced ─────────────────────────
export SHELL_GPU_TEST=1
export SHELL_NVIDIA_TEST=1
export SHELL_PKG_TEST=1
export SHELL_ENV_TEST=1
export SHELL_DEPLOY_TEST=1
export SHELL_NPM_GLOBALS_TEST=1
export SHELL_TAILSCALE_TEST=1
export SHELL_ZFS_TEST=1
export SHELL_PF_TEST=1
export SHELL_DESKTOP_TEST=1
export SETUP_IMPORT_TEST=1
# shell-ssh.sh and shell-system.sh use case/${0##*/} — no flag needed

# ── Source modules (functions only, nothing runs yet) ─────────────────────────
. "${SHARE}/build.cfg"
. "${SHARE}/firstboot/setup-import.sh"
. "${SHARE}/firstboot/shell-zfs.sh"
. "${SHARE}/firstboot/shell-gpu.sh"
. "${SHARE}/firstboot/shell-nvidia.sh"
. "${SHARE}/firstboot/shell-pkg.sh"
. "${SHARE}/firstboot/shell-ssh.sh"
. "${SHARE}/firstboot/shell-env.sh"
. "${SHARE}/firstboot/shell-system.sh"
. "${SHARE}/firstboot/shell-desktop.sh"
. "${SHARE}/firstboot/shell-pf.sh"
. "${SHARE}/firstboot/shell-tailscale.sh"
. "${SHARE}/firstboot/shell-npm-globals.sh"
. "${SHARE}/firstboot/shell-deploy.sh"

# ── Load GUI config if present ───────────────────────────────────────────────
SETUP_HANDOFF_LOADED=0
if [ -f "/tmp/clawdie-install.conf" ]; then
  log_msg "[firstboot] Loading GUI installer configuration"
  . "/tmp/clawdie-install.conf"
  step_done "wizard"
  SETUP_HANDOFF_LOADED=1
elif [ -f "/var/db/clawdie-installer/clawdie-handoff.sealed" ]; then
  log_msg "[firstboot] Loading installed handoff payload"
  . "/var/db/clawdie-installer/clawdie-handoff.sealed"
  step_done "wizard"
  SETUP_HANDOFF_LOADED=1
fi

log_msg "[firstboot] Starting — target: ${TARGET:-baremetal}${RESUME:+, resume mode}"
log_msg "[firstboot] ISO ${ISO_VERSION:-unknown}-${BUILD_CHANNEL:-unknown}; Clawdie-AI ${CLAWDIE_REF:-unknown}@${CLAWDIE_AI_COMMIT:-unknown}"

# ── Import setup.txt/system.env from USB config partition ───────────────────
if [ "$SETUP_HANDOFF_LOADED" -eq 1 ]; then
  log_msg "[firstboot] Skipping setup-import (installer handoff already loaded)"
  step_done "setup-import"
else
  run_step "setup-import" clawdie_setup_import_load "Load first-boot setup from USB partition"
fi

# ── ZFS pool detection (baremetal only) ───────────────────────────────────
# Runs early to decide boot mode: install | upgrade | maintenance
# Maintenance mode exec's away and never returns.
CLAWDIE_BOOT_MODE="${CLAWDIE_BOOT_MODE:-install}"
if [ "${TARGET:-baremetal}" != "vps" ]; then
  run_step "zfs" clawdie_shell_zfs_detect "ZFS pool detection"
fi
export CLAWDIE_BOOT_MODE

# ── Collect configuration ──────────────────────────────────────────────────
if step_completed "wizard"; then
  log_msg "[firstboot] Skipping wizard (already completed)"
elif [ "$CLAWDIE_BOOT_MODE" = "upgrade" ]; then
  # Upgrade: import existing pool, load .env from previous install
  log_msg "[firstboot] Upgrade mode — loading existing configuration"
  kldload zfs 2>/dev/null || true
  zpool import "$POOL_NAME" 2>/dev/null || true
  _existing_env="/home/clawdie/clawdie-ai/.env"
  if [ -f "$_existing_env" ]; then
    ENV_FILE="${ENV_FILE:-/home/clawdie/.env}"
    cp "$_existing_env" "$ENV_FILE"
    log_msg "[firstboot] Loaded existing .env from previous install"
  else
    log_msg "[firstboot] WARNING: No existing .env found — falling through to wizard"
  fi
  step_done "wizard"
elif [ "${TARGET:-baremetal}" = "vps" ]; then
  # VPS: all values must be pre-baked in build.cfg — validate
  [ -z "${ASSISTANT_NAME:-}" ] && log_msg "ERROR: ASSISTANT_NAME not baked" && exit 1
  [ -z "${AGENT_DOMAIN:-}" ]   && log_msg "ERROR: AGENT_DOMAIN not baked"   && exit 1
  [ -z "${TZ:-}" ]             && log_msg "ERROR: TZ not baked"              && exit 1
  log_msg "[firstboot] VPS — pre-baked config OK"
  step_done "wizard"
else
  # Baremetal: minimal wizard — identity, network, keys only
  # All jails (db, git/forgejo, cms) are provisioned by default.
  # API keys are deferred to web UI on first desktop login.
  _dialog() { bsddialog --backtitle "Clawdie-AI Setup" "$@" 2>&1; }

_dialog --msgbox "\
EXPERIMENTAL BUILD

This is pre-release software.
Not recommended for production use.
Data loss or service interruption possible.

By continuing, you assume all risks." 12 60

# Tailscale (recommended, but optional)
if _dialog --yesno \
  "Enable Tailscale for secure remote access?\n\n" \
  "Tailscale creates a private network for SSH access.\n" \
  "Without it, SSH will be exposed on public port 22.\n\n" \
  "Recommended: Yes (you can add auth key later if needed)" 14 70; then
  FEATURE_TAILSCALE="YES"
  TAILSCALE_AUTHKEY=$(_dialog --passwordbox \
    "Tailscale device auth key (tskey-...).\n\n" \
    "Leave blank to skip auth (you can run 'tailscale up' later).\n" \
    "Generate at: https://login.tailscale.com/admin/settings/keys" 13 72 "")
  if [ -z "$TAILSCALE_AUTHKEY" ]; then
    _dialog --msgbox "No auth key provided.\n\nTailscale will be installed but not authenticated.\nRun 'tailscale up' after first boot to connect." 10 60
  fi
else
  FEATURE_TAILSCALE="NO"
  TAILSCALE_AUTHKEY=""
  _dialog --msgbox "WARNING: SSH will be publicly accessible on port 22.\n\nYou are responsible for securing network access." 10 60
fi

ASSISTANT_NAME=$(_dialog --inputbox "Assistant name:" 8 50 "Clawdie")

# Derive default domain from assistant name (e.g., Clawdie → clawdie.home.arpa)
_agent_name_lower=$(echo "$ASSISTANT_NAME" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]//g')
_default_domain="${_agent_name_lower}.home.arpa"
AGENT_DOMAIN=$(_dialog --inputbox \
  "Domain zone (public or local):" 8 60 "$_default_domain")
if command -v route >/dev/null 2>&1 && command -v ifconfig >/dev/null 2>&1; then
  HOST_IF="$(route -n get default 2>/dev/null | awk '/interface:/ { print $2; exit }')"
  HOST_IPS=""
  if [ -n "$HOST_IF" ]; then
    HOST_IPS="$(ifconfig "$HOST_IF" 2>/dev/null | awk '/inet / { print $2 }')"
  fi
  if [ -z "$HOST_IPS" ]; then
    HOST_IPS="$(ifconfig 2>/dev/null | awk '/inet / && $2 != "127.0.0.1" { print $2 }')"
  fi
  if [ -n "$HOST_IPS" ]; then
    HOST_IPS_LINE="$(echo "$HOST_IPS" | tr '\n' ' ' | sed 's/ $//')"
    _dialog --msgbox "\
DNS note: If you use *.home.arpa, create an A record for
${AGENT_DOMAIN} pointing to this host IP.

Detected IP(s): ${HOST_IPS_LINE}" 10 70
  fi
fi
  TZ=$(_dialog --inputbox \
    "Timezone (e.g. Europe/Ljubljana):" 8 50 "UTC")
  SYSTEM_LOCALE=$(_dialog --inputbox \
    "Locale (e.g. en_US.UTF-8):" 8 50 "en_US.UTF-8")
  KEYMAP=$(_dialog --inputbox \
    "Console keymap (e.g. us):" 8 50 "us")
  [ -z "${SYSTEM_LOCALE:-}" ] && SYSTEM_LOCALE="en_US.UTF-8"
  DISPLAY_LOCALE="${SYSTEM_LOCALE}"
  ASSISTANT_LOCALE="${SYSTEM_LOCALE}"
  SSH_PUBLIC_KEY=$(_dialog --inputbox \
    "SSH public key (optional — paste ssh-ed25519 or ssh-rsa):" 12 70 "")

  # Defaults: all jails enabled, no local LLM (can be enabled post-install)
  : "${AGENT_GENDER:=f}"
  FEATURE_GIT="YES"
  FEATURE_GITEA="NO"
  CODE_HOSTING_MODE="git"
  LOCAL_LLM_PROVIDER="none"
  FEATURE_OLLAMA="NO"
  FEATURE_LLAMA_CPP="NO"
  FEATURE_OLLAMA_HPP="NO"

  # Summary screen
  SUMMARY_MSG="Configuration Summary:\n\n"
  SUMMARY_MSG+="Name: ${ASSISTANT_NAME}\n"
  SUMMARY_MSG+="Domain: ${AGENT_DOMAIN}\n"
  SUMMARY_MSG+="Timezone: ${TZ}\n"
  SUMMARY_MSG+="Locale: ${SYSTEM_LOCALE}\n"
  SUMMARY_MSG+="Keymap: ${KEYMAP}\n"
  SUMMARY_MSG+="SSH key: $([ -n "$SSH_PUBLIC_KEY" ] && echo "✓ Provided" || echo "✗ None")\n"
  if [ "${FEATURE_TAILSCALE}" = "YES" ]; then
    if [ -n "$TAILSCALE_AUTHKEY" ]; then
      SUMMARY_MSG+="Tailscale: ✓ Enabled (auth key provided)\n"
    else
      SUMMARY_MSG+="Tailscale: ⚠ Enabled (no auth key - run 'tailscale up' later)\n"
    fi
  else
    SUMMARY_MSG+="Tailscale: ✗ Disabled (SSH on public port 22)\n"
  fi
  SUMMARY_MSG+="Provider keys: configure after first boot in the controlplane\n"
  SUMMARY_MSG+="Telegram: configure after first boot in the controlplane\n"
  SUMMARY_MSG+="\nProceed with installation?"

  if ! _dialog --yesno "$SUMMARY_MSG" 16 70; then
    _dialog --msgbox "Installation cancelled. Rebooting..." 6 40
    reboot
  fi

  step_done "wizard"
fi

export CLAWDIE_BOOT_MODE POOL_NAME
export ASSISTANT_NAME AGENT_GENDER AGENT_DOMAIN TZ SSH_PUBLIC_KEY
export HOSTNAME INSTALL_MODE PROFILE
export SYSTEM_LOCALE DISPLAY_LOCALE ASSISTANT_LOCALE KEYMAP
export EMBED_BASE_URL EMBED_MODEL EMBED_API_KEY EMBED_DIMENSIONS
export FEATURE_TAILSCALE TAILSCALE_AUTHKEY
export CODE_HOSTING_MODE FEATURE_GIT FEATURE_GITEA FORGEJO_DISK_ESTIMATE
export LOCAL_LLM_PROVIDER FEATURE_OLLAMA FEATURE_LLAMA_CPP FEATURE_OLLAMA_HPP
export OLLAMA_RAM_ESTIMATE OLLAMA_DISK_ESTIMATE LLAMA_CPP_RAM_ESTIMATE LLAMA_CPP_DISK_ESTIMATE
export USB_LLM_MODELS_PATH
export ZFS_POOL ZFS_LAYOUT ZFS_DATA_DISKS ZFS_HOT_SPARES ZFS_PREFIX
export NETWORK_EXTERNAL_IF NETWORK_INTERNAL_IF TAILSCALE_IF ZFS_DISKS ZFS_SPARE_DISKS GPU_DEVICE SND_DEVICE

# ── Run modules ────────────────────────────────────────────────────────────
log_msg "[firstboot] Running modules..."

# Module execution matrix — controls which modules run in each mode.
# fresh install:  all modules
# upgrade:        pkg + env (append-only) + npm-globals + deploy only
# repair:         deploy only (targeted repair actions inside shell-deploy)
#
# GPU, SSH, system, desktop, PF, Tailscale are skipped on upgrade/repair —
# they would reset keys, overwrite rc.conf, and undo operator customisations.
run_step_if "install"          "gpu"        clawdie_shell_gpu_detect          "GPU driver detection" 1
run_step_if "install"          "nvidia"     clawdie_shell_nvidia_detect       "NVIDIA version selection" 2
run_step_if "install upgrade"  "pkg"        clawdie_shell_pkg_setup           "Package repo configuration" 3
run_step_if "install"          "ssh"        clawdie_shell_ssh_setup           "SSH keys + system passwords" 4
run_step_if "install upgrade"  "env"        clawdie_shell_env_generate        "Generate .env with secrets" 5
run_step_if "install"          "system"     clawdie_shell_system_config       "Hostname, rc.conf, services" 6
run_step_if "install"          "desktop"    clawdie_shell_desktop_detect      "Desktop enablement" 7
run_step_if "install"          "pf"         clawdie_shell_pf                  "PF firewall + jail NAT" 8
run_step_if "install"          "tailscale"  clawdie_shell_tailscale_setup     "Tailscale remote access" 8
run_step_if "install upgrade"  "npm-globals" clawdie_shell_npm_globals_install "Install bundled npm CLIs (pi)" 8
run_step_if "install upgrade"  "deploy"     clawdie_shell_deploy              "Extract tarball + just install" 8

log_msg "[firstboot] Complete."
log_msg "[firstboot] Aider (primary harness): aider --help"
log_msg "[firstboot] Pi (primary harness): pi --help"
log_msg "[firstboot] Optional: Codex CLI (headless): codex login --device-auth"
log_msg "[firstboot] Optional: Codex CLI (API key): printenv OPENAI_API_KEY | codex login --with-api-key"
