#!/usr/bin/env bash
# scripts/deploy-rig.sh — bring up the ophi118 OBS rig from scratch.
# Idempotent: re-running is safe, every phase checks before acting.
#
# Usage:
#   bash scripts/deploy-rig.sh                # interactive, full install
#   bash scripts/deploy-rig.sh --yes          # non-interactive, accept all prompts
#   bash scripts/deploy-rig.sh --check        # validate state, write nothing
#   bash scripts/deploy-rig.sh --rig-name X   # override hostname-derived rig name
#
# Run on a fresh Midgolem (or re-run on Ignia to verify). After:
#   git clone <repo> ~/.config/obs-studio
#   cd ~/.config/obs-studio
#   bash scripts/deploy-rig.sh
#
# What it covers:
#   1. Arch system packages (pacman + AUR for sansation-font)
#   2. Python user packages (python-mpd2, websockets)
#   3. Flatpak OBS Studio + filesystems=host override
#   4. Git submodules + asset directories under $HOME
#   5. PipeWire mpd_stream null sink + loopback
#   6. MPD config (zeroconf_name = this rig) + system units
#   7. Custom systemd --user units (bridge, bot)
#   8. OBS first-launch to seed obs-websocket plugin config
#   9. Secrets stub + manual next-steps report
set -euo pipefail

# ── globals ────────────────────────────────────────────────────────────────
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OBS_DIR="$(cd "$DIR/.." && pwd)"
RIG_NAME="${RIG_NAME:-$(hostname)}"
CHECK_ONLY=0
ASSUME_YES=0

# ── arg parsing ────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
  case "$1" in
    --check)     CHECK_ONLY=1; shift ;;
    --yes|-y)    ASSUME_YES=1; shift ;;
    --rig-name)  RIG_NAME="${2:?--rig-name needs an argument}"; shift 2 ;;
    -h|--help)   sed -n 's/^# \?//p' "$0" | head -28; exit 0 ;;
    *)           echo "unknown flag: $1" >&2; exit 2 ;;
  esac
done

RIG_NAME_TC="${RIG_NAME^}"   # title-case: ignia → Ignia

# ── output helpers ─────────────────────────────────────────────────────────
PHASE=0
TOTAL=9
step()  { PHASE=$((PHASE+1)); printf '\n\033[1;36m[%d/%d] %s\033[0m\n' "$PHASE" "$TOTAL" "$*"; }
ok()    { printf '  \033[32m✓\033[0m %s\n' "$*"; }
skip()  { printf '  \033[90m·\033[0m %s\n' "$*"; }
warn()  { printf '  \033[33m!\033[0m %s\n' "$*" >&2; }
err()   { printf '  \033[31m✗\033[0m %s\n' "$*" >&2; }
die()   { err "$*"; exit 1; }

confirm() {
  (( ASSUME_YES )) && return 0
  local q="$1"
  read -rp "  ? $q [y/N]: " a
  [[ "${a,,}" =~ ^(y|yes)$ ]]
}

would() {
  # In --check mode, prefix the message and skip the action.
  (( CHECK_ONLY )) && { skip "would: $*"; return 1; }
  return 0
}

# ── pre-flight ─────────────────────────────────────────────────────────────
[[ "$OBS_DIR" == "$HOME/.config/obs-studio" ]] \
  || die "Run this from \$HOME/.config/obs-studio (current OBS_DIR=$OBS_DIR)"

cat <<BANNER
╔═══════════════════════════════════════════════════════════════╗
║  ophi118 rig deployment                                       ║
║    rig name : $RIG_NAME_TC
║    config   : $OBS_DIR
║    mode     : $( ((CHECK_ONLY)) && echo '--check (no writes)' || echo 'install' )
║    confirms : $( ((ASSUME_YES)) && echo 'auto-yes' || echo 'interactive' )
╚═══════════════════════════════════════════════════════════════╝
BANNER

# ────────────────────────────────────────────────────────────────────────────
# Phase 1 — system packages
# ────────────────────────────────────────────────────────────────────────────
step "System packages (pacman)"

PACMAN_PKGS=(
  flatpak
  python python-pip
  pipewire pipewire-pulse pipewire-alsa wireplumber
  ffmpeg
  mpd mpc
  avahi nss-mdns
  fontconfig
)

if command -v pacman >/dev/null; then
  MISSING=()
  for p in "${PACMAN_PKGS[@]}"; do
    pacman -Qq "$p" >/dev/null 2>&1 || MISSING+=("$p")
  done
  if (( ${#MISSING[@]} == 0 )); then
    ok "all base packages installed"
  elif would "sudo pacman -S --needed ${MISSING[*]}"; then
    sudo pacman -S --needed $( ((ASSUME_YES)) && echo --noconfirm ) "${MISSING[@]}"
    ok "installed ${#MISSING[@]} package(s)"
  fi

  # NVIDIA branch
  if lspci 2>/dev/null | grep -qi 'vga.*nvidia'; then
    NV_MISSING=()
    for p in nvidia nvidia-utils; do
      pacman -Qq "$p" >/dev/null 2>&1 || NV_MISSING+=("$p")
    done
    if (( ${#NV_MISSING[@]} == 0 )); then
      ok "NVIDIA driver present"
    elif would "sudo pacman -S --needed ${NV_MISSING[*]}"; then
      sudo pacman -S --needed $( ((ASSUME_YES)) && echo --noconfirm ) "${NV_MISSING[@]}"
      ok "installed NVIDIA driver"
    fi
  else
    skip "no NVIDIA GPU detected (skipping nvidia/nvidia-utils)"
  fi

  # AUR: sansation-font (used by the `info` text source on Game scene).
  if pacman -Qq sansation-font >/dev/null 2>&1 \
     || fc-list 2>/dev/null | grep -qi 'sansation'; then
    ok "Sansation font present"
  else
    AUR_HELPER=""
    for h in paru yay; do command -v "$h" >/dev/null && { AUR_HELPER="$h"; break; }; done
    if [[ -z "$AUR_HELPER" ]]; then
      warn "Sansation font missing and no AUR helper (paru/yay) found"
      warn "  → install manually from AUR or accept the system-default fallback"
    elif would "$AUR_HELPER -S sansation-font"; then
      "$AUR_HELPER" -S $( ((ASSUME_YES)) && echo --noconfirm ) sansation-font || \
        warn "AUR install failed — install sansation-font manually if you want the Game scene 'info' text to render exactly"
    fi
  fi
else
  warn "pacman not found — this script targets Arch. Skipping system-package phase."
  warn "  Make sure these are installed by other means: ${PACMAN_PKGS[*]}"
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 2 — Python user packages
# ────────────────────────────────────────────────────────────────────────────
step "Python user packages"

PIP_PKGS=(python-mpd2 websockets)
PY_MISSING=()
for p in "${PIP_PKGS[@]}"; do
  python -c "import importlib, sys; importlib.import_module('${p//-/_}'.replace('python_mpd2','mpd'))" 2>/dev/null \
    || PY_MISSING+=("$p")
done
if (( ${#PY_MISSING[@]} == 0 )); then
  ok "python-mpd2 and websockets importable"
elif would "pip install --user ${PY_MISSING[*]}"; then
  python -m pip install --user --upgrade "${PY_MISSING[@]}"
  ok "installed ${PY_MISSING[*]}"
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 3 — OBS Flatpak
# ────────────────────────────────────────────────────────────────────────────
step "OBS Studio (Flatpak)"

if ! command -v flatpak >/dev/null; then
  die "flatpak not installed — phase 1 should have done this"
fi

if ! flatpak remotes --user 2>/dev/null | grep -q '^flathub' \
  && ! flatpak remotes 2>/dev/null | grep -q '^flathub'; then
  if would "flatpak remote-add flathub"; then
    flatpak remote-add --if-not-exists --user flathub \
      https://dl.flathub.org/repo/flathub.flatpakrepo
    ok "added Flathub remote"
  fi
else
  ok "Flathub remote present"
fi

if flatpak info com.obsproject.Studio >/dev/null 2>&1; then
  ok "OBS Studio flatpak installed"
else
  if would "flatpak install flathub com.obsproject.Studio"; then
    flatpak install $( ((ASSUME_YES)) && echo -y ) flathub com.obsproject.Studio
    ok "installed OBS Studio flatpak"
  fi
fi

if flatpak info --show-permissions com.obsproject.Studio 2>/dev/null \
   | grep -q 'filesystems=.*host'; then
  ok "filesystems=host override present (config dir reads from ~/.config/)"
else
  if would "flatpak override --user com.obsproject.Studio --filesystems=host"; then
    flatpak override --user com.obsproject.Studio --filesystems=host
    ok "added filesystems=host override"
  else
    warn "without filesystems=host, OBS will read from ~/.var/app/... not from this dir!"
  fi
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 4 — submodules + asset dirs
# ────────────────────────────────────────────────────────────────────────────
step "Submodules + asset directories"

if [[ -f "$OBS_DIR/.gitmodules" ]]; then
  if would "git submodule update --init --recursive"; then
    ( cd "$OBS_DIR" && git submodule update --init --recursive )
    ok "submodules initialized"
  fi
fi

ASSET_DIRS=(
  "$HOME/Videos"
  "$HOME/HDD/Music"
  "$HOME/HDD/Images/Gifs"
  "$HOME/cloud.jakubzych.com/_img/logos"
)
for d in "${ASSET_DIRS[@]}"; do
  if [[ -d "$d" ]]; then
    ok "exists: $d"
  elif would "mkdir -p $d"; then
    mkdir -p "$d"
    ok "created: $d"
  fi
done

ASSET_FILES=(
  "$HOME/HDD/Images/Gifs/stamd.png:Game-scene 'Starting Soon' image"
  "$HOME/cloud.jakubzych.com/_img/logos/dgw.png:Doomguard watermark"
)
MISSING_ASSETS=0
for entry in "${ASSET_FILES[@]}"; do
  path="${entry%%:*}"; what="${entry##*:}"
  if [[ -f "$path" ]]; then
    ok "asset present: $path"
  else
    warn "missing asset: $path  ($what)"
    MISSING_ASSETS=1
  fi
done
(( MISSING_ASSETS )) && warn "  → bring these over manually from Ignia; OBS will surface 'missing source' errors otherwise"

# ────────────────────────────────────────────────────────────────────────────
# Phase 5 — PipeWire mpd_stream sink
# ────────────────────────────────────────────────────────────────────────────
step "PipeWire null sink (mpd_stream)"

PW_CONF_DIR="$HOME/.config/pipewire/pipewire-pulse.conf.d"
PW_CONF="$PW_CONF_DIR/mpd-stream.conf"

if [[ -f "$PW_CONF" ]]; then
  ok "$PW_CONF already present"
else
  if would "write $PW_CONF"; then
    mkdir -p "$PW_CONF_DIR"
    cat > "$PW_CONF" <<'PWCONF'
# Persistent virtual sink for streaming MPD into OBS without mixing in
# desktop/browser/Discord audio.
#
#   MPD ──► null sink "mpd_stream"
#                │
#                ├──► loopback ──► default sink (so you still hear it on speakers)
#                │
#                └──► monitor ──► OBS "Audio Output Capture (PulseAudio) →
#                                       Monitor of MPD-Stream"
#
# Wired on the MPD side via target "mpd_stream" in ~/.config/mpd/mpd.conf.
# Loaded automatically when pipewire-pulse starts.

pulse.cmd = [
    {
        cmd  = "load-module"
        args = "module-null-sink sink_name=mpd_stream sink_properties=device.description=MPD-Stream"
    }
    {
        cmd  = "load-module"
        args = "module-loopback source=mpd_stream.monitor latency_msec=50"
    }
]
PWCONF
    ok "wrote $PW_CONF"
    if would "systemctl --user restart pipewire-pulse"; then
      systemctl --user restart pipewire-pulse.service 2>/dev/null || \
        warn "could not restart pipewire-pulse (will load on next login anyway)"
    fi
  fi
fi

# Verify the sink is actually loaded (only meaningful in install mode).
if (( ! CHECK_ONLY )); then
  sleep 1
  if pactl list short sinks 2>/dev/null | grep -q '^[0-9]*[[:space:]]\+mpd_stream\b'; then
    ok "mpd_stream sink loaded"
  else
    warn "mpd_stream sink NOT visible to pactl — restart pipewire-pulse or relog and re-check"
  fi
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 6 — MPD
# ────────────────────────────────────────────────────────────────────────────
step "MPD config + service"

MPD_DIR="$HOME/.config/mpd"
MPD_CONF="$MPD_DIR/mpd.conf"

write_mpd_conf() {
  mkdir -p "$MPD_DIR/playlists"
  cat > "$MPD_CONF" <<MPDCONF
# ~/.config/mpd/mpd.conf — generated by scripts/deploy-rig.sh.
# Re-run the deploy script to regenerate, or hand-edit for local tweaks.

music_directory     "~/HDD/Music"
playlist_directory  "~/.config/mpd/playlists"
db_file             "~/.config/mpd/database"
state_file          "~/.config/mpd/state"
sticker_file        "~/.config/mpd/sticker.sql"

log_file            "syslog"
auto_update         "yes"
restore_paused      "yes"
filesystem_charset  "UTF-8"

zeroconf_enabled    "yes"
zeroconf_name       "MPD on $RIG_NAME_TC"

audio_output {
    type    "pipewire"
    target  "mpd_stream"
    name    "MPD"
}
MPDCONF
}

if [[ -f "$MPD_CONF" ]]; then
  if grep -q "MPD on $RIG_NAME_TC" "$MPD_CONF"; then
    ok "$MPD_CONF already configured for $RIG_NAME_TC"
  else
    warn "$MPD_CONF exists but zeroconf_name doesn't match $RIG_NAME_TC"
    if confirm "overwrite $MPD_CONF?" && would "rewrite $MPD_CONF"; then
      cp "$MPD_CONF" "$MPD_CONF.before-deploy.$$"
      write_mpd_conf
      ok "rewrote $MPD_CONF (backup: $MPD_CONF.before-deploy.$$)"
    fi
  fi
else
  if would "write $MPD_CONF"; then
    write_mpd_conf
    ok "wrote $MPD_CONF"
  fi
fi

if systemctl --user is-enabled mpd.socket >/dev/null 2>&1; then
  ok "mpd.socket enabled"
else
  if would "systemctl --user enable --now mpd.socket mpd.service"; then
    systemctl --user enable --now mpd.socket mpd.service
    ok "enabled mpd.socket + mpd.service"
  fi
fi

if [[ -d "$HOME/HDD/Music" ]] && [[ -n "$(find "$HOME/HDD/Music" -maxdepth 3 -type f -print -quit 2>/dev/null)" ]]; then
  if would "mpc update"; then
    mpc update >/dev/null 2>&1 || warn "mpc update failed (mpd not yet running?)"
    ok "MPD library scan kicked off"
  fi
else
  warn "~/HDD/Music is empty — MPD will have no tracks until you populate it"
fi

# Avahi for zeroconf advertisement (system service).
if systemctl is-enabled avahi-daemon.service >/dev/null 2>&1; then
  ok "avahi-daemon enabled (system)"
else
  warn "avahi-daemon not enabled — MPD zeroconf won't advertise. To fix:"
  warn "    sudo systemctl enable --now avahi-daemon.service"
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 7 — custom systemd --user units
# ────────────────────────────────────────────────────────────────────────────
step "Systemd --user units (bridge + bot)"

UNIT_DIR="$HOME/.config/systemd/user"
mkdir -p "$UNIT_DIR"

write_unit() {
  local name body path
  name="$1"
  body="$2"
  path="$UNIT_DIR/$name"
  if [[ -f "$path" ]] && diff -q <(printf '%s' "$body") "$path" >/dev/null 2>&1; then
    ok "$name already up to date"
  elif would "write $path"; then
    printf '%s' "$body" > "$path"
    ok "wrote $path"
  fi
}

write_unit obs-mpd-bridge.service "[Unit]
Description=MPD → OBS WebSocket state bridge
Documentation=file:%h/.config/obs-studio/bridges/mpd-state.py
After=mpd.service mpd.socket pipewire.service
Wants=mpd.service

[Service]
Type=simple
ExecStart=/usr/bin/python3 %h/.config/obs-studio/bridges/mpd-state.py
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target
"

write_unit obs-twitch-bot.service "[Unit]
Description=Twitch chat → MPD control bot
Documentation=file:%h/.config/obs-studio/twitch-bot/bot.py
After=mpd.service mpd.socket network-online.target
Wants=mpd.service network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 %h/.config/obs-studio/twitch-bot/bot.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
"

if would "systemctl --user daemon-reload"; then
  systemctl --user daemon-reload
fi

# bridge can start now (no secrets needed)
if systemctl --user is-enabled obs-mpd-bridge.service >/dev/null 2>&1; then
  ok "obs-mpd-bridge.service enabled"
else
  if would "systemctl --user enable --now obs-mpd-bridge.service"; then
    systemctl --user enable --now obs-mpd-bridge.service || \
      warn "obs-mpd-bridge failed to start — needs vendor/obs-config.js (see phase 8)"
  fi
fi

# bot must NOT auto-start until .env is populated; just enable it
if systemctl --user is-enabled obs-twitch-bot.service >/dev/null 2>&1; then
  ok "obs-twitch-bot.service enabled (start manually after .env is filled)"
else
  if would "systemctl --user enable obs-twitch-bot.service"; then
    systemctl --user enable obs-twitch-bot.service
    ok "enabled obs-twitch-bot.service (NOT started — needs .env)"
  fi
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 8 — OBS first-launch + WebSocket bootstrap
# ────────────────────────────────────────────────────────────────────────────
step "OBS first-launch + WebSocket bootstrap"

WS_CFG="$OBS_DIR/plugin_config/obs-websocket/config.json"

if [[ -f "$WS_CFG" ]]; then
  ok "obs-websocket config exists ($WS_CFG)"
else
  if would "launch OBS once to seed plugin_config"; then
    echo "  → launching OBS once to seed plugin configs."
    echo "    Close the OBS window when it appears (or wait — script auto-stops it)."
    flatpak run com.obsproject.Studio >/dev/null 2>&1 &
    OBS_PID=$!
    for _ in $(seq 1 60); do
      [[ -f "$WS_CFG" ]] && break
      sleep 1
    done
    kill "$OBS_PID" 2>/dev/null || true
    wait "$OBS_PID" 2>/dev/null || true
    if [[ -f "$WS_CFG" ]]; then
      ok "obs-websocket config seeded"
    else
      warn "config not seeded after 60s — start OBS manually once and re-run"
    fi
  fi
fi

if [[ -f "$WS_CFG" ]]; then
  if python3 -c "import json, sys; sys.exit(0 if json.load(open('$WS_CFG')).get('server_enabled') else 1)" 2>/dev/null; then
    ok "obs-websocket server_enabled = true"
  else
    if would "set server_enabled=true in $WS_CFG"; then
      python3 - "$WS_CFG" <<'PY'
import json, sys
p = sys.argv[1]
d = json.load(open(p))
d['server_enabled'] = True
json.dump(d, open(p, 'w'), indent=4)
PY
      ok "set server_enabled=true (takes effect on next OBS launch)"
    fi
  fi

  if [[ -f "$OBS_DIR/vendor/obs-config.js" ]]; then
    ok "vendor/obs-config.js present"
  else
    if would "bash scripts/setup.sh"; then
      bash "$DIR/setup.sh" || warn "setup.sh failed — re-run after OBS WS port is reachable"
    fi
  fi
fi

# ────────────────────────────────────────────────────────────────────────────
# Phase 9 — secrets stub + final report
# ────────────────────────────────────────────────────────────────────────────
step "Secrets stub + final report"

stub_env() {
  local target="$1"
  if [[ -f "$target" ]]; then
    ok "$target exists (leaving alone)"
    return
  fi
  if [[ -f "$OBS_DIR/twitch-bot/.env.example" ]] && would "stub $target from .env.example"; then
    cp "$OBS_DIR/twitch-bot/.env.example" "$target"
    chmod 600 "$target"
    ok "stubbed $target (chmod 600)"
  fi
}

stub_env "$OBS_DIR/twitch-bot/.env"
stub_env "$OBS_DIR/twitch-bot/.env.ophi118"

# Refresh telemetry so the rig name is correct on this machine.
if (( ! CHECK_ONLY )); then
  if would "scripts/telemetry.sh --collect --no-review (rig snapshot)"; then
    bash "$DIR/telemetry.sh" --collect --no-review || \
      warn "telemetry.sh --collect failed — re-run interactively to review"
    ok "telemetry refreshed (rig=$RIG_NAME_TC)"
  fi
fi

cat <<DONE

╔═══════════════════════════════════════════════════════════════╗
║  Deployment complete — manual steps remaining:                ║
╚═══════════════════════════════════════════════════════════════╝

  1. Twitch bot token   (chat:read chat:edit)
       https://twitchtokengenerator.com
       → fill twitch-bot/.env

  2. Broadcaster token  (channel:manage:broadcast)
       https://twitchtokengenerator.com
       → fill twitch-bot/.env.ophi118

  3. Twitch stream key
       Twitch Dashboard → Settings → Stream → Copy stream key
       → paste into OBS Settings → Stream
         (saved to basic/profiles/ophi118/service.json)

  4. Once .env is populated:
       systemctl --user restart obs-twitch-bot.service

  5. Review telemetry interactively:
       bash scripts/telemetry.sh --collect

  6. Launch OBS:
       flatpak run com.obsproject.Studio

Sanity checks:
  pactl list short sinks | grep mpd_stream
  systemctl --user status mpd obs-mpd-bridge obs-twitch-bot
  mpc status
  ss -tlnp | grep 4455

DONE
