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

PROFILE_NAME="${CHATGPT_COMPUTER_MCP_PROFILE:-chatgpt-computer-mcp}"
SERVICE_NAME="chatgpt-computer-mcp-tunnel.service"
PNPM_VERSION="11.20.0"
REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
SECRETS_DIR="$REPO/.secrets"
TUNNEL_FILE="$SECRETS_DIR/tunnel-id"
API_FILE="$SECRETS_DIR/runtime-api-key"
CONFIG_TEMPLATE="$REPO/config.example.json"
LOCAL_CONFIG="$REPO/config.local.json"
USER_LIB="$HOME/.local/lib/chatgpt-computer-mcp"
USER_BIN="$HOME/.local/bin"
SYSTEMD_DIR="$HOME/.config/systemd/user"
PROFILE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/tunnel-client"
PROFILE_FILE="$PROFILE_DIR/$PROFILE_NAME.yaml"
ASSUME_YES=false
INSTALL_DESKTOP=true

say()  { printf '\n==> %s\n' "$*"; }
info() { printf '    %s\n' "$*"; }
warn() { printf 'WARNING: %s\n' "$*" >&2; }
die()  { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<'USAGE'
Usage: ./install.sh [--yes] [--no-desktop]

Installs and activates chatgpt-computer-mcp through OpenAI Secure MCP Tunnel.

Before running, create:
  1. A tunnel at https://platform.openai.com/settings/organization/tunnels
  2. A runtime API key at https://platform.openai.com/settings/organization/api-keys

The installer asks for both values if they are not already supplied through:
  CONTROL_PLANE_TUNNEL_ID
  CONTROL_PLANE_API_KEY

Options:
  --yes         accept the visual-computer configuration without prompting
  --no-desktop  do not attempt to install X11/Wayland host build/runtime packages
  -h, --help    show this help
USAGE
}

while (($#)); do
  case "$1" in
    --yes) ASSUME_YES=true ;;
    --no-desktop) INSTALL_DESKTOP=false ;;
    -h|--help) usage; exit 0 ;;
    *) die "Unknown option: $1" ;;
  esac
  shift
done

trim() {
  local value="$1"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

read_secret_file() {
  local file="$1" line value
  [[ -r "$file" ]] || return 1
  line="$(grep -m1 -vE '^[[:space:]]*(#|$)' "$file" 2>/dev/null || true)"
  line="$(trim "${line%$'\r'}")"
  [[ -n "$line" ]] || return 1
  [[ "$line" == export\ * ]] && line="${line#export }"
  if [[ "$line" == *=* ]]; then value="${line#*=}"; else value="$line"; fi
  value="$(trim "$value")"
  if [[ ${#value} -ge 2 ]]; then
    if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then value="${value:1:${#value}-2}"; fi
    if [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then value="${value:1:${#value}-2}"; fi
  fi
  [[ -n "$value" ]] || return 1
  printf '%s' "$value"
}

store_secret() {
  local file="$1" value="$2"
  umask 077
  printf '%s\n' "$value" > "$file"
  chmod 600 "$file"
}

load_credentials() {
  mkdir -p "$SECRETS_DIR"
  chmod 700 "$SECRETS_DIR"

  local tunnel_id="${CONTROL_PLANE_TUNNEL_ID:-}"
  local api_key="${CONTROL_PLANE_API_KEY:-}"

  [[ -n "$tunnel_id" ]] || tunnel_id="$(read_secret_file "$TUNNEL_FILE" 2>/dev/null || true)"
  [[ -n "$api_key" ]] || api_key="$(read_secret_file "$API_FILE" 2>/dev/null || true)"

  if [[ -z "$tunnel_id" ]]; then
    [[ -t 0 ]] || die "Missing tunnel ID. Set CONTROL_PLANE_TUNNEL_ID or rerun interactively."
    printf 'OpenAI tunnel ID (tunnel_...): '
    IFS= read -r tunnel_id
    tunnel_id="$(trim "$tunnel_id")"
  fi
  [[ "$tunnel_id" =~ ^tunnel_[0-9a-f]{32}$ ]] || die "Tunnel ID must be tunnel_ followed by 32 lowercase hexadecimal characters."

  if [[ -z "$api_key" ]]; then
    [[ -t 0 ]] || die "Missing runtime API key. Set CONTROL_PLANE_API_KEY or rerun interactively."
    printf 'OpenAI runtime API key (input hidden): '
    IFS= read -rs api_key
    printf '\n'
    api_key="$(trim "$api_key")"
  fi
  [[ -n "$api_key" ]] || die "Runtime API key cannot be empty."

  store_secret "$TUNNEL_FILE" "$tunnel_id"
  store_secret "$API_FILE" "$api_key"
  export CONTROL_PLANE_TUNNEL_ID="$tunnel_id"
  export CONTROL_PLANE_API_KEY="$api_key"
}

install_apt_packages() {
  (($#)) || return 0
  command -v apt-get >/dev/null 2>&1 || return 1
  if [[ $(id -u) -eq 0 ]]; then
    apt-get update && apt-get install -y "$@"
  elif command -v sudo >/dev/null 2>&1; then
    sudo apt-get update && sudo apt-get install -y "$@"
  else
    return 1
  fi
}

ensure_base_tools() {
  local missing=()
  command -v curl >/dev/null 2>&1 || missing+=(curl)
  command -v unzip >/dev/null 2>&1 || missing+=(unzip)
  command -v python3 >/dev/null 2>&1 || missing+=(python3)
  if ((${#missing[@]})); then
    say "Installing installer prerequisites: ${missing[*]}"
    install_apt_packages "${missing[@]}" || die "Install these prerequisites and rerun: ${missing[*]}"
  fi
}

select_pnpm() {
  if command -v corepack >/dev/null 2>&1 && corepack "pnpm@$PNPM_VERSION" --version >/dev/null 2>&1; then
    PNPM_CMD=(corepack "pnpm@$PNPM_VERSION")
    return
  fi
  if command -v npx >/dev/null 2>&1; then
    PNPM_CMD=(npx -y "pnpm@$PNPM_VERSION")
    return
  fi
  die "Need Corepack or npx to run pnpm $PNPM_VERSION."
}

install_tunnel_client() {
  if command -v tunnel-client >/dev/null 2>&1; then
    TUNNEL_CLIENT="$(command -v tunnel-client)"
    return
  fi

  ensure_base_tools
  local os arch target tmp metadata asset_url checksum_url archive found companion checksum_line
  os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  case "$(uname -m)" in
    x86_64|amd64) arch=amd64 ;;
    aarch64|arm64) arch=arm64 ;;
    *) die "Unsupported architecture for automatic tunnel-client install: $(uname -m)" ;;
  esac
  [[ "$os" == linux ]] || die "Automatic tunnel-client install supports Linux only. Download it from https://github.com/openai/tunnel-client/releases/latest"
  target="$os-$arch.zip"

  say "Installing the latest official OpenAI tunnel-client"
  tmp="$(mktemp -d)"
  metadata="$tmp/release.json"
  curl -fsSL -H 'Accept: application/vnd.github+json' https://api.github.com/repos/openai/tunnel-client/releases/latest -o "$metadata" \
    || die "Could not query the official tunnel-client release."

  asset_url="$(python3 - "$metadata" "$target" <<'PY'
import json, sys
release=json.load(open(sys.argv[1], encoding='utf-8'))
target=sys.argv[2]
for asset in release.get('assets', []):
    name=asset.get('name','')
    if name == target or name.endswith('-'+target):
        print(asset.get('browser_download_url','')); break
PY
)"
  checksum_url="$(python3 - "$metadata" <<'PY'
import json, sys
release=json.load(open(sys.argv[1], encoding='utf-8'))
for asset in release.get('assets', []):
    if asset.get('name') == 'SHA256SUMS.txt':
        print(asset.get('browser_download_url','')); break
PY
)"
  [[ -n "$asset_url" ]] || die "Latest tunnel-client release has no $target archive."

  archive="$tmp/$target"
  curl -fL "$asset_url" -o "$archive"
  if [[ -n "$checksum_url" ]] && command -v sha256sum >/dev/null 2>&1; then
    curl -fsSL "$checksum_url" -o "$tmp/SHA256SUMS.txt"
    checksum_line="$(grep -E "[[:space:]]+\*?$target$" "$tmp/SHA256SUMS.txt" | head -n1 || true)"
    [[ -n "$checksum_line" ]] || die "Official checksum file did not contain $target."
    (cd "$tmp" && printf '%s\n' "$checksum_line" | sha256sum -c -)
  fi

  unzip -q "$archive" -d "$tmp/unpacked"
  found="$(find "$tmp/unpacked" -type f -name tunnel-client -print -quit)"
  [[ -n "$found" ]] || die "Downloaded archive did not contain tunnel-client."
  mkdir -p "$USER_BIN"
  install -m 0755 "$found" "$USER_BIN/tunnel-client"
  companion="$(find "$tmp/unpacked" -type f -name cloudflared -print -quit)"
  [[ -z "$companion" ]] || install -m 0755 "$companion" "$USER_BIN/cloudflared"
  TUNNEL_CLIENT="$USER_BIN/tunnel-client"
  export PATH="$USER_BIN:$PATH"
}

install_desktop_packages() {
  $INSTALL_DESKTOP || return 0
  local need=()
  command -v xrandr >/dev/null 2>&1 || need+=(x11-xserver-utils)
  command -v ffmpeg >/dev/null 2>&1 || need+=(ffmpeg)
  command -v xdotool >/dev/null 2>&1 || need+=(xdotool)
  command -v cc >/dev/null 2>&1 || need+=(build-essential)
  command -v pkg-config >/dev/null 2>&1 || need+=(pkg-config)
  for spec in \
    'libei-1.0:libei-dev' \
    'libpipewire-0.3:libpipewire-0.3-dev' \
    'libspa-0.2:libspa-0.2-dev' \
    'gio-unix-2.0:libglib2.0-dev' \
    'libpng:libpng-dev' \
    'xkbcommon:libxkbcommon-dev'; do
    module="${spec%%:*}"
    package="${spec#*:}"
    pkg-config --exists "$module" 2>/dev/null || need+=("$package")
  done
  if ! command -v xdg-desktop-portal >/dev/null 2>&1 \
    && [[ ! -x /usr/libexec/xdg-desktop-portal ]] \
    && [[ ! -x /usr/lib/xdg-desktop-portal/xdg-desktop-portal ]]; then
    need+=(xdg-desktop-portal)
  fi
  ((${#need[@]})) || return 0
  mapfile -t need < <(printf '%s\n' "${need[@]}" | awk '!seen[$0]++')
  say "Installing Linux desktop/native dependencies: ${need[*]}"
  install_apt_packages "${need[@]}" || die "Could not install required desktop/native dependencies: ${need[*]}"
}

write_launcher() {
  mkdir -p "$USER_LIB"
  cat > "$USER_LIB/run-tunnel.sh" <<'LAUNCHER'
#!/usr/bin/env bash
set -Eeuo pipefail
: "${CHATGPT_COMPUTER_MCP_REPO:?CHATGPT_COMPUTER_MCP_REPO is required}"
: "${TUNNEL_CLIENT_BIN:?TUNNEL_CLIENT_BIN is required}"
: "${CHATGPT_COMPUTER_MCP_PROFILE:?CHATGPT_COMPUTER_MCP_PROFILE is required}"
API_FILE="$CHATGPT_COMPUTER_MCP_REPO/.secrets/runtime-api-key"
[[ -r "$API_FILE" ]] || { echo "Missing runtime API key: $API_FILE" >&2; exit 1; }
export CONTROL_PLANE_API_KEY="$(head -n1 "$API_FILE" | tr -d '\r\n')"
export CHATGPT_COMPUTER_MCP_CONFIG="$CHATGPT_COMPUTER_MCP_REPO/config.local.json"
export HEALTH_LISTEN_ADDR="127.0.0.1:0"
if [[ -z "${DISPLAY:-}" && -S /tmp/.X11-unix/X0 ]]; then export DISPLAY=:0; fi
if [[ -z "${XAUTHORITY:-}" && -f "$HOME/.Xauthority" ]]; then export XAUTHORITY="$HOME/.Xauthority"; fi
exec "$TUNNEL_CLIENT_BIN" run --profile "$CHATGPT_COMPUTER_MCP_PROFILE"
LAUNCHER
  chmod 700 "$USER_LIB/run-tunnel.sh"
}

write_service() {
  mkdir -p "$SYSTEMD_DIR"
  cat > "$SYSTEMD_DIR/$SERVICE_NAME" <<SERVICE
[Unit]
Description=OpenAI Secure MCP Tunnel for chatgpt-computer-mcp
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
WorkingDirectory=$REPO
Environment=CHATGPT_COMPUTER_MCP_REPO=$REPO
Environment=TUNNEL_CLIENT_BIN=$TUNNEL_CLIENT
Environment=CHATGPT_COMPUTER_MCP_PROFILE=$PROFILE_NAME
Environment=TUNNEL_CLIENT_PROFILE_DIR=$PROFILE_DIR
ExecStart=$USER_LIB/run-tunnel.sh
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
SERVICE
}

main() {
  [[ "$(uname -s)" == Linux ]] || die "This installer currently targets Linux."
  [[ -f "$REPO/package.json" && -f "$REPO/src/stdio.ts" ]] || die "Run this script from a chatgpt-computer-mcp checkout."

  say "ChatGPT-Computer-MCP one-command installer"
  info "Repository: $REPO"
  printf '\nThis installer enables visual observation and desktop input according to config.example.json.\n'
  if ! $ASSUME_YES; then
    [[ -t 0 ]] || die "Non-interactive install requires --yes."
    printf 'Type YES to continue: '
    local answer
    IFS= read -r answer
    [[ "$answer" == YES ]] || die "Installation cancelled."
  fi

  mkdir -p "$SECRETS_DIR"; chmod 700 "$SECRETS_DIR"
  load_credentials
  command -v node >/dev/null 2>&1 || die "Node.js 22+ is required."
  local node_major
  node_major="$(node -p 'Number(process.versions.node.split(`.`)[0])')"
  (( node_major >= 22 )) || die "Node.js 22+ is required; found $(node --version)."
  select_pnpm
  install_tunnel_client
  command -v systemctl >/dev/null 2>&1 || die "systemd/systemctl is required for the persistent user service."
  systemctl --user show-environment >/dev/null 2>&1 || die "A working systemd user session is required."

  install_desktop_packages

  say "Installing dependencies and running the full gate with pnpm $PNPM_VERSION"
  cd "$REPO"
  "${PNPM_CMD[@]}" install --no-frozen-lockfile
  "${PNPM_CMD[@]}" gate

  say "Writing local visual-computer configuration"
  [[ -f "$CONFIG_TEMPLATE" ]] || die "Missing $CONFIG_TEMPLATE"
  [[ ! -f "$LOCAL_CONFIG" ]] || cp -p "$LOCAL_CONFIG" "$SECRETS_DIR/config.local.json.backup.$(date +%Y%m%d-%H%M%S)"
  cp "$CONFIG_TEMPLATE" "$LOCAL_CONFIG"
  chmod 600 "$LOCAL_CONFIG"
  say "Checking the MCP stdio entrypoint"
  [[ -f "$REPO/dist/src/stdio.js" ]] || die "Build did not produce dist/src/stdio.js"
  local stdio_rc
  set +e
  CHATGPT_COMPUTER_MCP_CONFIG="$LOCAL_CONFIG" timeout 1s node "$REPO/dist/src/stdio.js" >/dev/null 2>"$SECRETS_DIR/stdio-check.err"
  stdio_rc=$?
  set -e
  if [[ $stdio_rc -ne 124 && $stdio_rc -ne 0 ]]; then
    cat "$SECRETS_DIR/stdio-check.err" >&2 || true
    die "Local MCP stdio process exited unexpectedly (status $stdio_rc)."
  fi
  rm -f "$SECRETS_DIR/stdio-check.err"

  say "Creating tunnel-client profile '$PROFILE_NAME'"
  mkdir -p "$PROFILE_DIR"; chmod 700 "$PROFILE_DIR"
  [[ ! -f "$PROFILE_FILE" ]] || mv "$PROFILE_FILE" "$PROFILE_FILE.backup.$(date +%Y%m%d-%H%M%S)"
  export CHATGPT_COMPUTER_MCP_CONFIG="$LOCAL_CONFIG"
  export TUNNEL_CLIENT_PROFILE_DIR="$PROFILE_DIR"
  export HEALTH_LISTEN_ADDR="127.0.0.1:0"
  "$TUNNEL_CLIENT" init \
    --sample sample_mcp_stdio_local \
    --profile "$PROFILE_NAME" \
    --tunnel-id "$CONTROL_PLANE_TUNNEL_ID" \
    --mcp-command "$(command -v node) $REPO/dist/src/stdio.js"

  say "Running tunnel diagnostics"
  "$TUNNEL_CLIENT" doctor --profile "$PROFILE_NAME" --explain

  say "Installing persistent systemd user service"
  write_launcher
  write_service
  systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR XDG_SESSION_TYPE 2>/dev/null || true
  systemctl --user daemon-reload
  systemctl --user enable --now "$SERVICE_NAME"
  sleep 3
  if ! systemctl --user is-active --quiet "$SERVICE_NAME"; then
    journalctl --user -u "$SERVICE_NAME" -n 80 --no-pager >&2 || true
    die "Tunnel service did not stay active."
  fi

  say "Final verification"
  "$TUNNEL_CLIENT" doctor --profile "$PROFILE_NAME" --explain

  printf '\n============================================================\n'
  printf 'LOCAL SETUP COMPLETE\n'
  printf '============================================================\n'
  printf 'Profile:        %s\n' "$PROFILE_NAME"
  printf 'Tunnel service: ACTIVE\n'
  printf '\nRemaining ChatGPT steps:\n'
  printf '  1. Enable ChatGPT Developer mode.\n'
  printf '  2. Open https://chatgpt.com/plugins\n'
  printf '  3. Create a developer-mode app with Connection = Tunnel.\n'
  printf '  4. Select this tunnel, enable the app, and call computer.info.\n'
  printf '\nCheck later with: ./scripts/status.sh\n'
}

main
