#!/usr/bin/env bash
# BPShowServer macOS installer
# Usage: sudo bash install.sh
#        sudo bash install.sh --cluster-join 10.0.10.4:7443 --cluster-invite CODE \
#            --i-understand-config-will-be-replaced
#   Joining a cluster is destructive (docs/04-clustering-and-ha.md §2): it replaces
#   this node's local show/config with the leader's desired state, so the explicit
#   acknowledgment flag is required alongside --cluster-join/--cluster-invite.

set -euo pipefail

# ── Constants ─────────────────────────────────────────────────────────────────

INSTALL_DIR="/usr/local/lib/bpshowserver"
CLI_LINK="/usr/local/bin/bpssctl"
PLIST_PATH="/Library/LaunchDaemons/io.bpshow.bpshowserver.plist"
LOG_DIR="/usr/local/var/log/bpshowserver"
SERVICE_LABEL="io.bpshow.bpshowserver"
PORT=7474

# ── Optional cluster-join flags (light touch — default install flow is unchanged) ──

CLUSTER_JOIN=""
CLUSTER_INVITE=""
CLUSTER_JOIN_ACK=0

while [ $# -gt 0 ]; do
    case "$1" in
        --cluster-join)
            CLUSTER_JOIN="${2:-}"
            shift 2
            ;;
        --cluster-invite)
            CLUSTER_INVITE="${2:-}"
            shift 2
            ;;
        --i-understand-config-will-be-replaced)
            CLUSTER_JOIN_ACK=1
            shift
            ;;
        *)
            echo "Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

# ── Require root ──────────────────────────────────────────────────────────────

if [ "$EUID" -ne 0 ]; then
    echo "This installer must be run with sudo."
    echo "  sudo bash install.sh"
    exit 1
fi

# ── Locate this script's directory (inside the extracted tarball) ─────────────

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ── Read version + arch markers written by PackageInstallerMac.ps1 ────────────

VERSION=""
if [ -f "$SCRIPT_DIR/VERSION" ]; then
    VERSION=" v$(cat "$SCRIPT_DIR/VERSION")"
fi

# ── Verify architecture ───────────────────────────────────────────────────────
# The .arch file contains "x64" or "arm64" (the RID suffix used at build time).
# This catches the common mistake of extracting the wrong tarball.

MACHINE_ARCH="$(uname -m)"

if [ -f "$SCRIPT_DIR/.arch" ]; then
    TARBALL_ARCH="$(cat "$SCRIPT_DIR/.arch")"

    if [[ "$TARBALL_ARCH" == "arm64" && "$MACHINE_ARCH" != "arm64" ]]; then
        echo "ERROR: This is an Intel Mac ($MACHINE_ARCH) but this tarball targets Apple Silicon (arm64)."
        echo "       Extract BPShowServer-*-mac-x64.tar.gz instead, then re-run."
        exit 1
    fi

    if [[ "$TARBALL_ARCH" == "x64" && "$MACHINE_ARCH" != "x86_64" ]]; then
        echo "ERROR: This is an Apple Silicon Mac ($MACHINE_ARCH) but this tarball targets Intel (x64)."
        echo "       Extract BPShowServer-*-mac-arm64.tar.gz instead, then re-run."
        exit 1
    fi

    echo "Architecture: $MACHINE_ARCH (tarball: $TARBALL_ARCH) -- OK"
else
    echo "WARNING: .arch marker not found; skipping architecture check."
fi

# ── Stop existing service if present ──────────────────────────────────────────

if [ -f "$PLIST_PATH" ]; then
    echo "Stopping existing service..."
    launchctl unload "$PLIST_PATH" 2>/dev/null || true
fi

# ── Install files ─────────────────────────────────────────────────────────────

echo "Installing to $INSTALL_DIR ..."
rm -rf "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
cp -R "$SCRIPT_DIR/." "$INSTALL_DIR/"
chmod +x "$INSTALL_DIR/BPShowServer"

# Symlink bpssctl CLI if present
if [ -f "$INSTALL_DIR/bpssctl" ]; then
    chmod +x "$INSTALL_DIR/bpssctl"
    ln -sf "$INSTALL_DIR/bpssctl" "$CLI_LINK"
    echo "  Symlinked bpssctl -> $CLI_LINK"
fi

# LiveKit intercom sidecar (bundled per-arch by dotnet publish). Cross-compiled
# tarballs built on Windows lose the exec bit, so restore it here.
if [ -f "$INSTALL_DIR/livekit/livekit-server" ]; then
    chmod +x "$INSTALL_DIR/livekit/livekit-server"
    echo "  Found LiveKit intercom sidecar"
fi

# BPMediaNode Desktop companion (Electron GPU → engine → NDI/OMT). Optional;
# present when publish harvested tools/bpshow-medianode into medianode/
# (legacy webout/ still accepted).
chmod_medianode() {
    local f="$1"
    if [ -f "$f" ]; then
        chmod +x "$f"
        return 0
    fi
    return 1
}
if chmod_medianode "$INSTALL_DIR/medianode/bpshow-medianode" \
    || chmod_medianode "$INSTALL_DIR/medianode/linux-unpacked/bpshow-medianode"; then
    echo "  Found BPMediaNode Desktop companion"
elif [ -d "$INSTALL_DIR/medianode/BPMediaNode Desktop.app" ]; then
    chmod +x "$INSTALL_DIR/medianode/BPMediaNode Desktop.app/Contents/MacOS/bpshow-medianode" 2>/dev/null || true
    chmod +x "$INSTALL_DIR/medianode/BPMediaNode Desktop.app/Contents/MacOS/BPMediaNode Desktop" 2>/dev/null || true
    echo "  Found BPMediaNode Desktop companion (.app)"
elif [ -d "$INSTALL_DIR/medianode/bpshow-medianode.app" ]; then
    chmod +x "$INSTALL_DIR/medianode/bpshow-medianode.app/Contents/MacOS/bpshow-medianode" 2>/dev/null || true
    echo "  Found BPMediaNode Desktop companion (.app)"
elif chmod_medianode "$INSTALL_DIR/webout/bpshow-webout" \
    || chmod_medianode "$INSTALL_DIR/webout/bpshow-medianode"; then
    echo "  Found MediaNode/WebOut companion (legacy webout/)"
elif [ -d "$INSTALL_DIR/webout/bpshow-webout.app" ]; then
    chmod +x "$INSTALL_DIR/webout/bpshow-webout.app/Contents/MacOS/bpshow-webout" 2>/dev/null || true
    echo "  Found WebOut companion (.app)"
elif [ -f "$INSTALL_DIR/webout/linux-unpacked/bpshow-webout" ]; then
    chmod +x "$INSTALL_DIR/webout/linux-unpacked/bpshow-webout"
    echo "  Found WebOut companion (linux-unpacked)"
fi

# Self-contained medianode-engine next to Electron (extraResources / afterPack).
# Cross-compiled publishes lose the exec bit.
for root in "$INSTALL_DIR/medianode" "$INSTALL_DIR/webout"; do
    [ -d "$root" ] || continue
    find "$root" -type f -name 'medianode-engine' 2>/dev/null | while read -r eng; do
        chmod +x "$eng" 2>/dev/null || true
        echo "  Found medianode-engine (${eng#"$INSTALL_DIR"/})"
    done
done

# ── Clear Gatekeeper quarantine ───────────────────────────────────────────────
# BPShowServer is not code-signed. Remove the quarantine extended attribute so
# macOS does not block execution. You can also right-click -> Open on first launch.
xattr -cr "$INSTALL_DIR" 2>/dev/null || true

# ── Ad-hoc code signing ────────────────────────────────────────────────────────
# On Apple Silicon, the kernel's code-signing enforcement (AMFI) refuses to
# execute ANY unsigned binary at all -- it gets SIGKILLed before main() even
# runs, with no crash report and no log output, which then looks like an
# instant launchd crash-loop. Clearing the quarantine xattr above is NOT
# sufficient on arm64 (unlike Intel Macs, where Gatekeeper's quarantine
# prompt is the only obstacle). An ad-hoc signature (no certificate/Apple
# ID needed) satisfies the kernel's "must have some signature" requirement.
# Harmless to apply on Intel too, so we always sign both binaries.
echo "Applying ad-hoc code signature..."
codesign --force --deep -s - "$INSTALL_DIR/BPShowServer" \
    && echo "  Signed BPShowServer" \
    || echo "  WARNING: codesign failed for BPShowServer -- app may fail to launch on Apple Silicon"

if [ -f "$INSTALL_DIR/bpssctl" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/bpssctl" \
        && echo "  Signed bpssctl" \
        || echo "  WARNING: codesign failed for bpssctl"
fi

if [ -f "$INSTALL_DIR/livekit/livekit-server" ]; then
    codesign --force -s - "$INSTALL_DIR/livekit/livekit-server" \
        && echo "  Signed livekit-server" \
        || echo "  WARNING: codesign failed for livekit-server -- intercom sidecar may fail to launch on Apple Silicon"
fi

if [ -d "$INSTALL_DIR/medianode/BPMediaNode Desktop.app" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/medianode/BPMediaNode Desktop.app" \
        && echo "  Signed BPMediaNode Desktop.app" \
        || echo "  WARNING: codesign failed for BPMediaNode Desktop.app"
elif [ -d "$INSTALL_DIR/medianode/bpshow-medianode.app" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/medianode/bpshow-medianode.app" \
        && echo "  Signed bpshow-medianode.app" \
        || echo "  WARNING: codesign failed for bpshow-medianode.app"
elif [ -f "$INSTALL_DIR/medianode/bpshow-medianode" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/medianode/bpshow-medianode" \
        && echo "  Signed bpshow-medianode" \
        || echo "  WARNING: codesign failed for bpshow-medianode"
elif [ -d "$INSTALL_DIR/webout/bpshow-webout.app" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/webout/bpshow-webout.app" \
        && echo "  Signed bpshow-webout.app" \
        || echo "  WARNING: codesign failed for bpshow-webout.app"
elif [ -f "$INSTALL_DIR/webout/bpshow-webout" ]; then
    codesign --force --deep -s - "$INSTALL_DIR/webout/bpshow-webout" \
        && echo "  Signed bpshow-webout" \
        || echo "  WARNING: codesign failed for bpshow-webout"
fi

# ── CryptoKit rpath shim ──────────────────────────────────────────────────────
# The .NET app host references CryptoKit via @rpath, which dyld resolves
# relative to the executable directory.  /System/Library/Frameworks is not in
# the rpath list, so we place a local shim that points at the real binary.
# Both the flat path and the legacy Versions/A path are covered so the shim
# works regardless of whether the binary was patched at build time.
CRYPTOKIT_REAL="/System/Library/Frameworks/CryptoKit.framework/CryptoKit"
if [ -f "$CRYPTOKIT_REAL" ]; then
    mkdir -p "$INSTALL_DIR/CryptoKit.framework/Versions/A"
    ln -sf "$CRYPTOKIT_REAL" "$INSTALL_DIR/CryptoKit.framework/CryptoKit"
    ln -sf "$CRYPTOKIT_REAL" "$INSTALL_DIR/CryptoKit.framework/Versions/A/CryptoKit"
    echo "  Created CryptoKit.framework shim"
fi

# ── Data directory ────────────────────────────────────────────────────────────
# Create under the real (non-root) user's home directory.

REAL_USER="${SUDO_USER:-$USER}"
REAL_HOME="$(eval echo "~$REAL_USER")"
DATA_DIR="$REAL_HOME/Library/Application Support/BPShowServer"
sudo -u "$REAL_USER" mkdir -p "$DATA_DIR"
echo "  Data directory: $DATA_DIR"

# ── Log directory ─────────────────────────────────────────────────────────────

mkdir -p "$LOG_DIR"
echo "  Log directory:  $LOG_DIR"

# ── launchd plist ─────────────────────────────────────────────────────────────
# System LaunchDaemon (runs as root, survives logout). PortAudio freezes the
# CoreAudio device list at Pa_Initialize; HAL plugins such as Dante Virtual
# Soundcard often finish loading a few seconds after boot. A short start delay
# plus AudioEngine's macOS stabilize/rescan (when running a build that includes
# those) prevents the Local Audio device picker from missing DVS.
# Manual refresh (newer builds): POST /api/v1/audio/devices/rescan

# Wrapper delays process start so CoreAudio HAL plugins are visible to PortAudio.
WRAPPER="$INSTALL_DIR/start-bpshowserver.sh"
cat > "$WRAPPER" <<'WRAP_EOF'
#!/bin/bash
# Delay LaunchDaemon start so late CoreAudio HAL devices (e.g. Dante Virtual
# Soundcard) exist before PortAudio snapshots the device list.
sleep 15
exec /usr/local/lib/bpshowserver/BPShowServer --mode node
WRAP_EOF
chmod +x "$WRAPPER"

cat > "$PLIST_PATH" <<'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>io.bpshow.bpshowserver</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/lib/bpshowserver/start-bpshowserver.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/usr/local/var/log/bpshowserver/stdout.log</string>
  <key>StandardErrorPath</key>
  <string>/usr/local/var/log/bpshowserver/stderr.log</string>
  <key>WorkingDirectory</key>
  <string>/usr/local/lib/bpshowserver</string>
</dict>
</plist>
PLIST_EOF

chmod 644 "$PLIST_PATH"
echo "  Wrote $PLIST_PATH"

# ── Load service ──────────────────────────────────────────────────────────────

echo "Loading launchd service..."
launchctl load "$PLIST_PATH"

# ── Optional: join a cluster (docs/04-clustering-and-ha.md §2) ─────────────────

if [ -n "$CLUSTER_JOIN" ]; then
    if [ -z "$CLUSTER_INVITE" ] || [ "$CLUSTER_JOIN_ACK" -ne 1 ]; then
        echo "WARNING: --cluster-join requires both --cluster-invite and --i-understand-config-will-be-replaced. Skipping cluster join."
    elif [ ! -x "$CLI_LINK" ]; then
        echo "WARNING: bpssctl not found; skipping cluster join. Run it manually once the service is up:"
        echo "  bpssctl cluster join $CLUSTER_JOIN --invite <code> --i-understand-config-will-be-replaced"
    else
        echo ""
        echo "Waiting for the service to come up before joining the cluster..."
        # start-bpshowserver.sh itself delays ~15s for CoreAudio HAL devices, so poll
        # rather than guessing a single fixed sleep.
        for _ in $(seq 1 30); do
            curl -fsS "http://localhost:$PORT/api/v1/health" >/dev/null 2>&1 && break
            sleep 2
        done
        echo "Joining cluster at $CLUSTER_JOIN..."
        if "$CLI_LINK" cluster join "$CLUSTER_JOIN" --invite "$CLUSTER_INVITE" --i-understand-config-will-be-replaced; then
            echo "  Cluster join complete."
        else
            echo "WARNING: Cluster join failed (see above). The service is still installed and running standalone."
        fi
    fi
fi

# ── Optional web UI launch ───────────────────────────────────────────────────
# Automated deploys must not create a browser window for every version update.
# A person performing an interactive install can explicitly opt in.

if [ "${BPSHOW_OPEN_WEB_UI:-0}" = "1" ]; then
    echo "Opening web UI in 18 seconds (service start delay)..."
    (sleep 18 && sudo -u "$REAL_USER" open "http://localhost:$PORT") &
else
    echo "Web UI browser launch disabled (set BPSHOW_OPEN_WEB_UI=1 to opt in)."
fi

# ── Summary ───────────────────────────────────────────────────────────────────

echo ""
echo "✓ BPShowServer${VERSION} installed"
echo "  Web UI:  http://localhost:$PORT"
echo "  Logs:    $LOG_DIR/"
echo "  Stop:    sudo launchctl unload $PLIST_PATH"
echo "  Start:   sudo launchctl load   $PLIST_PATH"
echo "  CLI:     bpssctl --help"
echo ""
echo "Note: If macOS blocks the app (Gatekeeper), run:"
echo "  sudo xattr -cr $INSTALL_DIR"
echo ""
