#!/usr/bin/env bash
set -euo pipefail

# Serialize installs/restarts for this user. Re-running the curl command while a
# previous verification loop is active used to launch a second kiosk publisher.
lock_dir="${XDG_RUNTIME_DIR:-/tmp}/robopark-kiosk-${UID}.lock"
if ! mkdir "$lock_dir" 2>/dev/null; then
  echo "Another RoboPark kiosk install/restart is already running. Wait for it to finish, or stop that shell with Ctrl-C." >&2
  exit 5
fi
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT

device_id="${1:-}"
origin="${2:-https://robo2.tail45774e.ts.net:3543}"
origin="${origin%/}"

if [[ ! "$device_id" =~ ^[a-f0-9]{32}$ ]]; then
  echo "Missing or invalid robot device ID" >&2
  exit 2
fi

if [[ ! "$origin" =~ ^https://[A-Za-z0-9.-]+(:[0-9]+)?$ ]]; then
  echo "Invalid HTTPS production origin: $origin" >&2
  exit 2
fi

firefox_bin="$(command -v firefox || true)"
if [[ -z "$firefox_bin" ]]; then
  echo "Firefox is not installed" >&2
  exit 1
fi

echo "Preflight: validating $device_id against $origin"
registry="$(curl -fsSL --connect-timeout 5 --max-time 12 "$origin/robopark/api/devices")"
if ! printf '%s' "$registry" | grep -q "\"$device_id\""; then
  echo "Device ID is not registered on the V2 control center; production was not changed" >&2
  exit 3
fi

distribution_dir="$(dirname "$(readlink -f "$firefox_bin")")/distribution"
system_policy_dir="/etc/firefox/policies"
tmp_policy="$(mktemp)"
backup_dir="$HOME/.robopark-kiosk-backup"
mkdir -p "$backup_dir"
policy_path="$system_policy_dir/policies.json"
service_path="$HOME/.config/systemd/user/robopark-kiosk.service"
recovery_path="$HOME/.local/bin/robopark-media-recovery"
recovery_service_path="$HOME/.config/systemd/user/robopark-media-recovery.service"
[[ -f "$policy_path" ]] && sudo cp -f "$policy_path" "$backup_dir/policies.json" || true
[[ -f "$service_path" ]] && cp -f "$service_path" "$backup_dir/robopark-kiosk.service" || true

rollback() {
  echo "V2 verification failed; restoring the previous production kiosk" >&2
  if [[ -f "$backup_dir/policies.json" ]]; then sudo install -m 0644 "$backup_dir/policies.json" "$policy_path"; fi
  if [[ -f "$backup_dir/robopark-kiosk.service" ]]; then cp -f "$backup_dir/robopark-kiosk.service" "$service_path"; fi
  systemctl --user daemon-reload 2>/dev/null || true
  systemctl --user restart robopark-kiosk.service 2>/dev/null || true
}
trap 'rm -f "$tmp_policy"; rmdir "$lock_dir" 2>/dev/null || true' EXIT

cat >"$tmp_policy" <<JSON
{
  "policies": {
    "Permissions": {
      "Camera": { "Allow": ["$origin"], "BlockNewRequests": false, "Locked": true },
      "Microphone": { "Allow": ["$origin"], "BlockNewRequests": false, "Locked": true },
      "Autoplay": { "Allow": ["$origin"], "Default": "block-audio-video", "Locked": true }
    },
    "Preferences": {
      "media.autoplay.default": { "Value": 0, "Status": "locked" },
      "media.autoplay.blocking_policy": { "Value": 0, "Status": "locked" }
    }
  }
}
JSON

sudo install -d -m 0755 "$system_policy_dir"
sudo install -m 0644 "$tmp_policy" "$system_policy_dir/policies.json"
# Traditional distro Firefox also reads its adjacent distribution directory.
# Snap packages are read-only there, so failure is deliberately non-fatal.
if sudo install -d -m 0755 "$distribution_dir" 2>/dev/null; then
  sudo install -m 0644 "$tmp_policy" "$distribution_dir/policies.json" 2>/dev/null || true
fi

join_url="$origin/?device_id=$device_id&autostart=1&hardware_autobind=1"
export DISPLAY="${DISPLAY:-:0}"
export XAUTHORITY="${XAUTHORITY:-$HOME/.Xauthority}"

# Bind playback at the PipeWire/Pulse layer. Browser setSinkId is retained as
# a second guard, but unattended Firefox cannot reliably repair a stale HDMI
# default on every Raspberry Pi image.
if command -v pactl >/dev/null 2>&1; then
  usb_sink="$(pactl list short sinks 2>/dev/null | awk 'BEGIN{IGNORECASE=1} /usb/{print $2; exit}')"
  [[ -z "$usb_sink" ]] && usb_sink="$(pactl list short sinks 2>/dev/null | awk 'NR==1{print $2}')"
  if [[ -n "$usb_sink" ]]; then
    pactl set-default-sink "$usb_sink" || true
    pactl set-sink-mute "$usb_sink" 0 || true
    pactl set-sink-volume "$usb_sink" 100% || true
    echo "Audio output bound through PulseAudio: $usb_sink"
  fi
elif command -v wpctl >/dev/null 2>&1; then
  usb_sink_id="$(wpctl status -n 2>/dev/null | awk 'BEGIN{IGNORECASE=1; sinks=0} /Sinks:/{sinks=1;next} /Sources:/{sinks=0} sinks && /usb/{line=$0; sub(/^.*[[:space:]]/,"",line); if(match($0,/[0-9]+\./)){id=substr($0,RSTART,RLENGTH-1); print id; exit}}')"
  if [[ -n "$usb_sink_id" ]]; then
    wpctl set-default "$usb_sink_id" || true
    wpctl set-mute "$usb_sink_id" 0 || true
    wpctl set-volume "$usb_sink_id" 1.0 || true
    echo "Audio output bound through PipeWire: $usb_sink_id"
  fi
fi

mkdir -p "$HOME/.config/systemd/user"
cat >"$HOME/.config/systemd/user/robopark-kiosk.service" <<UNIT
[Unit]
Description=RoboPark production kiosk
After=graphical-session.target network-online.target
Wants=network-online.target

[Service]
Environment=DISPLAY=${DISPLAY}
Environment=XAUTHORITY=${XAUTHORITY}
ExecStart=${firefox_bin} --kiosk ${join_url}
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
UNIT

# A small unprivileged control worker lets the central UI release media devices
# held by duplicate browser/test processes. It deliberately never terminates
# PipeWire, WirePlumber, PulseAudio, SSH, or unrelated system services.
mkdir -p "$HOME/.local/bin" "$HOME/.local/state/robopark"
cat >"$recovery_path" <<'RECOVERY'
#!/usr/bin/env bash
set -u
device_id="$1"
origin="${2%/}"
state="$HOME/.local/state/robopark/kill-media-revision"
while sleep 2; do
  payload="$(curl -fsSL --connect-timeout 3 --max-time 6 "$origin/api/robopark/devices/$device_id/control" 2>/dev/null || true)"
  revision="$(printf '%s' "$payload" | sed -n 's/.*"kill_competitors_revision"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p')"
  [[ -z "$revision" ]] && continue
  if [[ ! -f "$state" ]]; then printf '%s\n' "$revision" >"$state"; continue; fi
  previous="$(cat "$state" 2>/dev/null || printf 0)"
  [[ "$revision" -le "$previous" ]] && continue
  printf '%s\n' "$revision" >"$state"
  systemctl --user stop robopark-kiosk.service 2>/dev/null || true
  systemctl --user kill --kill-who=all robopark-kiosk.service 2>/dev/null || true
  pkill -TERM -f 'firefox[^ ]*.*robo2\.tail45774e\.ts\.net:(3443|3543)' 2>/dev/null || true
  for name in firefox firefox-esr firefox-bin chromium chromium-browser ffmpeg gst-launch-1.0 cheese guvcview libcamera-vid libcamera-still rpicam-vid rpicam-still arecord speaker-test; do
    pkill -TERM -x "$name" 2>/dev/null || true
  done
  sleep 2
  for node in /dev/video0 /dev/video1; do
    [[ -e "$node" ]] && fuser -k "$node" 2>/dev/null || true
  done
  systemctl --user start robopark-kiosk.service 2>/dev/null || true
done
RECOVERY
chmod 0755 "$recovery_path"
cat >"$recovery_service_path" <<UNIT
[Unit]
Description=RoboPark guarded media recovery worker
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=${recovery_path} ${device_id} ${origin}
Restart=always
RestartSec=3

[Install]
WantedBy=default.target
UNIT
systemctl --user daemon-reload || true
systemctl --user enable robopark-kiosk.service || true
systemctl --user enable --now robopark-media-recovery.service || true

# Stop the managed cgroup first, then close only Firefox-family processes that
# are attached to this RoboPark origin. `pkill -x firefox` misses firefox-esr
# and firefox-bin on some Pi images, leaving duplicate LiveKit publishers.
systemctl --user stop robopark-kiosk.service 2>/dev/null || true
systemctl --user kill --kill-who=all robopark-kiosk.service 2>/dev/null || true
pkill -TERM -f 'firefox[^ ]*.*robo2\.tail45774e\.ts\.net:3543' 2>/dev/null || true
pkill -TERM -x firefox 2>/dev/null || true
pkill -TERM -x firefox-esr 2>/dev/null || true
pkill -TERM -x firefox-bin 2>/dev/null || true
# Release stale user-level capture/playback clients before Firefox asks
# PipeWire for the production devices. Never terminate PipeWire,
# WirePlumber, PulseAudio, SSH, or unrelated services.
for name in chromium chromium-browser ffmpeg gst-launch-1.0 cheese guvcview libcamera-vid libcamera-still rpicam-vid rpicam-still arecord speaker-test; do
  pkill -TERM -x "$name" 2>/dev/null || true
done
for _ in 1 2 3 4 5; do
  pgrep -u "$UID" -f 'firefox|firefox-esr|firefox-bin' >/dev/null || break
  sleep 1
done
# Escalate only for this user's remaining Firefox family after the graceful
# wait. A stale browser otherwise keeps the microphone/camera and LiveKit room.
if pgrep -u "$UID" -f 'firefox|firefox-esr|firefox-bin' >/dev/null; then
  pkill -KILL -u "$UID" -f 'firefox|firefox-esr|firefox-bin' 2>/dev/null || true
  sleep 1
fi
# A crashed camera client can survive under a different executable name. Free
# only the two conventional USB-camera nodes after graceful process cleanup;
# do not touch /dev/snd because PipeWire legitimately owns those handles.
if command -v fuser >/dev/null 2>&1; then
  for node in /dev/video0 /dev/video1; do
    [[ -e "$node" ]] && fuser -k "$node" 2>/dev/null || true
  done
  sleep 1
fi

if systemctl --user restart robopark-kiosk.service 2>/dev/null; then
  echo "Persistent kiosk service started"
else
  nohup "$firefox_bin" --kiosk "$join_url" >"$HOME/robovoice-kiosk.log" 2>&1 &
fi

echo "Verifying V2 room join (allowing up to 120 seconds for Pi media initialization)..."
verified=0
for attempt in $(seq 1 60); do
  robots="$(curl -fsSL --connect-timeout 3 --max-time 6 "$origin/robopark/api/robots" 2>/dev/null || true)"
  # `status` may legitimately be `degraded` while audio and the voice worker
  # are live (for example, a camera-less/blocked Vixen). Migration success is
  # the physical kiosk connection, represented by connection_status=online.
  if printf '%s' "$robots" | grep -q "\"id\":\"$device_id\"[^}]*\"connection_status\":\"online\""; then verified=1; break; fi
  if (( attempt % 10 == 0 )); then
    echo "Still waiting for $device_id ($((attempt * 2))s): Firefox may be completing microphone/camera initialization..."
  fi
  sleep 2
done
if [[ "$verified" != 1 ]]; then
  echo "V2 did not observe the registered device after 120 seconds." >&2
  systemctl --user status robopark-kiosk.service --no-pager -l >&2 || true
  journalctl --user -u robopark-kiosk.service -n 40 --no-pager >&2 || true
  rollback
  exit 4
fi

echo "V2 migration verified: $join_url"
echo "Rollback snapshot: $backup_dir"
