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

YES=0
PKG_MANAGER=""
SUDO=()
SUDO_CMD=""
APT_UPDATED=0
PACKAGE_BASE_URL="https://packages.playit.gg"
PLAYIT_APT_KEY_URL=""
PLAYIT_ALPINE_KEY_URL=""
PLAYIT_ALPINE_REPO_URL=""
PLAYIT_APT_KEYRING="/usr/share/keyrings/playit.gpg"

usage() {
  cat <<'EOF'
Usage: install.sh [OPTIONS]

Install the playit package repository and playit package.

Options:
  -y, --yes                       Run non-interactively and accept all prompts
  --override-package-url URL      Override https://packages.playit.gg
  -h, --help                      Print this help text
EOF
}

has_cmd() {
  command -v "$1" >/dev/null 2>&1
}

tty_available() {
  [ -r /dev/tty ] && { : </dev/tty; } 2>/dev/null
}

quote_cmd() {
  local out=""
  local arg

  for arg in "$@"; do
    if [ -n "${out}" ]; then
      out+=" "
    fi
    printf -v arg "%q" "${arg}"
    out+="${arg}"
  done

  printf "%s\n" "${out}"
}

ask_yes_no() {
  local question="$1"
  local default="${2:-yes}"
  local prompt="[Y/n]"
  local answer

  if [ "${YES}" -eq 1 ]; then
    return 0
  fi

  if [ "${default}" != "yes" ]; then
    prompt="[y/N]"
  fi

  if ! tty_available; then
    echo "Interactive mode requires a terminal. Re-run with -y to accept prompts automatically." >&2
    exit 1
  fi

  printf "%s %s: " "${question}" "${prompt}" >/dev/tty
  if ! read -r answer </dev/tty; then
    echo "Aborted." >&2
    exit 1
  fi

  case "${answer}" in
    "" )
      [ "${default}" = "yes" ]
      ;;
    y|Y|yes|YES|Yes )
      return 0
      ;;
    * )
      return 1
      ;;
  esac
}

confirm_or_abort() {
  if ! ask_yes_no "Run this command?" yes; then
    echo "Aborted." >&2
    exit 1
  fi
}

run_step() {
  local description="$1"
  shift

  echo
  echo "${description}"
  echo "\$ $(quote_cmd "$@")"
  confirm_or_abort
  "$@"
}

run_shell_step() {
  local description="$1"
  local command_string="$2"

  echo
  echo "${description}"
  echo "\$ ${command_string}"
  confirm_or_abort
  "${BASH:-bash}" -euo pipefail -c "${command_string}"
}

run_apt_step() {
  local description="$1"
  shift

  if [ "${YES}" -eq 1 ]; then
    run_step "${description}" "${SUDO[@]}" env DEBIAN_FRONTEND=noninteractive "$@"
  else
    run_step "${description}" "${SUDO[@]}" "$@"
  fi
}

run_apt_update_step() {
  local description="$1"

  run_apt_step \
    "${description}" \
    apt-get \
    -o Acquire::GzipIndexes=false \
    -o Acquire::CompressionTypes::Order::=gz \
    -o Acquire::CompressionTypes::gz=false \
    update
}

require_sudo() {
  if [ "${EUID}" -eq 0 ]; then
    SUDO=()
    SUDO_CMD=""
    return
  fi

  if ! has_cmd sudo; then
    echo "This installer needs root privileges. Please install sudo or run this script as root." >&2
    exit 1
  fi

  SUDO=(sudo)
  SUDO_CMD="sudo "
}

detect_package_manager() {
  if has_cmd apk; then
    PKG_MANAGER="apk"
  elif has_cmd apt-get; then
    PKG_MANAGER="apt-get"
  elif has_cmd dnf; then
    PKG_MANAGER="dnf"
  elif has_cmd yum; then
    PKG_MANAGER="yum"
  elif has_cmd zypper; then
    PKG_MANAGER="zypper"
  elif has_cmd pacman; then
    PKG_MANAGER="pacman"
  else
    echo "Unsupported system: install.sh supports apt, dnf/yum, zypper, apk, and pacman." >&2
    echo "Manual installation instructions are available in README.md." >&2
    exit 1
  fi
}

system_uses_systemd() {
  has_cmd systemctl || [ -d /run/systemd/system ] || [ -d /etc/systemd/system ]
}

system_uses_openrc() {
  has_cmd rc-service || has_cmd openrc-run || [ -d /run/openrc ] || [ -d /etc/init.d ]
}

alpine_playit_package() {
  if system_uses_openrc && ! system_uses_systemd; then
    printf "%s\n" "playit-openrc"
    return
  fi

  printf "%s\n" "playit"
}

pkg_for_command() {
  local command_name="$1"

  case "${PKG_MANAGER}:${command_name}" in
    apt-get:curl) echo curl ;;
    apt-get:wget) echo wget ;;
    apt-get:gpg) echo gnupg ;;
    apt-get:tee) echo coreutils ;;
    apt-get:grep) echo grep ;;
    dnf:curl|yum:curl|zypper:curl|apk:curl) echo curl ;;
    dnf:wget|yum:wget|zypper:wget|apk:wget) echo wget ;;
    dnf:gpg|yum:gpg) echo gnupg2 ;;
    zypper:gpg) echo gpg2 ;;
    apk:gpg) echo gnupg ;;
    dnf:tee|yum:tee|zypper:tee|apk:tee) echo coreutils ;;
    dnf:grep|yum:grep|zypper:grep|apk:grep) echo grep ;;
    *) echo "${command_name}" ;;
  esac
}

ca_certificates_missing() {
  [ ! -e /etc/ssl/certs/ca-certificates.crt ] && [ ! -e /etc/ssl/cert.pem ]
}

append_unique_package() {
  local package="$1"
  local existing

  for existing in "${MISSING_PACKAGES[@]}"; do
    if [ "${existing}" = "${package}" ]; then
      return
    fi
  done

  MISSING_PACKAGES+=("${package}")
}

install_prereqs() {
  if [ "$#" -eq 0 ]; then
    return
  fi

  require_sudo

  case "${PKG_MANAGER}" in
    apt-get)
      if [ "${APT_UPDATED}" -eq 0 ]; then
        run_apt_update_step "Update APT package metadata before installing installer prerequisites."
        APT_UPDATED=1
      fi
      run_apt_step "Install tools needed by the playit installer." apt-get install -y "$@"
      ;;
    dnf)
      run_step "Install tools needed by the playit installer." "${SUDO[@]}" dnf install -y "$@"
      ;;
    yum)
      run_step "Install tools needed by the playit installer." "${SUDO[@]}" yum install -y "$@"
      ;;
    zypper)
      run_step "Install tools needed by the playit installer." "${SUDO[@]}" zypper install -y "$@"
      ;;
    apk)
      run_step "Install tools needed by the playit installer." "${SUDO[@]}" apk add "$@"
      ;;
  esac
}

ensure_commands() {
  local description="$1"
  shift
  local command_name
  MISSING_PACKAGES=()

  for command_name in "$@"; do
    if ! has_cmd "${command_name}"; then
      append_unique_package "$(pkg_for_command "${command_name}")"
    fi
  done

  if ca_certificates_missing; then
    append_unique_package ca-certificates
  fi

  if [ "${#MISSING_PACKAGES[@]}" -ne 0 ]; then
    echo
    echo "${description}"
    install_prereqs "${MISSING_PACKAGES[@]}"
  fi
}

download_to() {
  local url="$1"
  local dest="$2"
  local preferred="${3:-curl}"

  if [ "${preferred}" = "wget" ] && has_cmd wget; then
    run_step "Download ${url} to ${dest}." "${SUDO[@]}" wget -O "${dest}" "${url}"
  else
    run_step "Download ${url} to ${dest}." "${SUDO[@]}" curl -fsSL -o "${dest}" "${url}"
  fi
}

download_stdout_cmd() {
  local url="$1"

  if has_cmd curl; then
    printf "curl -SsL %q" "${url}"
  else
    printf "wget -qO- %q" "${url}"
  fi
}

configure_package_urls() {
  PACKAGE_BASE_URL="${PACKAGE_BASE_URL%/}"

  PLAYIT_APT_KEY_URL="${PACKAGE_BASE_URL}/keys/playit.gpg"
  PLAYIT_ALPINE_KEY_URL="${PACKAGE_BASE_URL}/keys/playit.rsa.pub"
  PLAYIT_ALPINE_REPO_URL="${PACKAGE_BASE_URL}/data/alpine/stable/main"
}

install_debian() {
  require_sudo
  ensure_commands "Install missing tools required to add the playit APT repository." curl gpg tee

  run_step "Create the APT keyring directory." "${SUDO[@]}" mkdir -p /usr/share/keyrings

  run_shell_step \
    "Install the playit APT signing key." \
    "$(download_stdout_cmd "${PLAYIT_APT_KEY_URL}") | gpg --dearmor | ${SUDO_CMD}tee ${PLAYIT_APT_KEYRING} >/dev/null"

  run_step "Set APT signing key permissions." "${SUDO[@]}" chmod 0644 "${PLAYIT_APT_KEYRING}"

  if [ -e /etc/apt/sources.list.d/playit-cloud.list ]; then
    run_step "Remove the legacy playit APT source list." "${SUDO[@]}" rm -f /etc/apt/sources.list.d/playit-cloud.list
  fi

  run_shell_step \
    "Configure the playit APT repository." \
    "printf '%s\n' 'deb [signed-by=${PLAYIT_APT_KEYRING}] ${PACKAGE_BASE_URL}/data/debian ./' | ${SUDO_CMD}tee /etc/apt/sources.list.d/playit.list >/dev/null"

  run_apt_update_step "Update APT package metadata."
  APT_UPDATED=1
  run_apt_step "Install playit." apt-get install -y playit
}

install_fedora_like() {
  local manager="$1"

  require_sudo
  ensure_commands "Install missing tools required to add the playit RPM repository." curl

  run_shell_step \
    "Configure the playit RPM repository." \
    "cat <<'EOF' | ${SUDO_CMD}tee /etc/yum.repos.d/playit.repo >/dev/null
[playit]
name=playit
baseurl=${PACKAGE_BASE_URL}/data/rpm/\$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=${PACKAGE_BASE_URL}/keys/playit.gpg
metadata_expire=1h
EOF"

  run_step "Install playit." "${SUDO[@]}" "${manager}" install -y playit
}

install_opensuse() {
  require_sudo
  run_step "Create the zypper repository directory." "${SUDO[@]}" mkdir -p /etc/zypp/repos.d

  run_shell_step \
    "Configure the playit openSUSE repository." \
    "cat <<'EOF' | ${SUDO_CMD}tee /etc/zypp/repos.d/playit.repo >/dev/null
[playit]
name=playit
enabled=1
autorefresh=1
baseurl=${PACKAGE_BASE_URL}/data/rpm/\$basearch
type=rpm-md
gpgcheck=1
repo_gpgcheck=1
gpgkey=${PACKAGE_BASE_URL}/keys/playit.gpg
EOF"

  run_step "Refresh zypper repository metadata." "${SUDO[@]}" zypper --non-interactive --gpg-auto-import-keys refresh
  run_step "Install playit." "${SUDO[@]}" zypper --non-interactive install -y playit
}

install_alpine() {
  local playit_package

  require_sudo
  ensure_commands "Install missing tools required to add the playit Alpine repository." wget grep tee

  download_to "${PLAYIT_ALPINE_KEY_URL}" /etc/apk/keys/playit.rsa.pub wget

  run_shell_step \
    "Add the playit Alpine repository if it is not already configured." \
    "grep -qxF '${PLAYIT_ALPINE_REPO_URL}' /etc/apk/repositories || echo '${PLAYIT_ALPINE_REPO_URL}' | ${SUDO_CMD}tee -a /etc/apk/repositories >/dev/null"

  run_step "Update Alpine package metadata." "${SUDO[@]}" apk update
  playit_package=$(alpine_playit_package)
  run_step "Install ${playit_package}." "${SUDO[@]}" apk add "${playit_package}"
}

install_arch() {
  local aur_helper
  local aur_args

  if [ "${EUID}" -eq 0 ]; then
    echo "Arch AUR packages should not be installed as root. Re-run this installer as a normal user with yay or paru installed." >&2
    exit 1
  fi

  if has_cmd yay; then
    aur_helper="yay"
    aur_args=(-S --noconfirm --answerclean None --answerdiff None playit-bin)
  elif has_cmd paru; then
    aur_helper="paru"
    aur_args=(-S --noconfirm --skipreview playit-bin)
  else
    echo "Arch Linux support requires an AUR helper. Install yay or paru, then run: yay -S playit-bin" >&2
    exit 1
  fi

  run_step "Install playit from the AUR." "${aur_helper}" "${aur_args[@]}"
}

playit_target_user() {
  if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
    printf "%s\n" "${SUDO_USER}"
    return 0
  fi

  if [ "${EUID}" -ne 0 ] && [ -n "${USER:-}" ]; then
    printf "%s\n" "${USER}"
    return 0
  fi

  return 1
}

playit_group_exists() {
  if has_cmd getent; then
    getent group playit >/dev/null 2>&1
    return
  fi

  grep -q '^playit:' /etc/group 2>/dev/null
}

user_in_playit_group() {
  local user="$1"

  id -nG "${user}" 2>/dev/null | tr ' ' '\n' | grep -qx playit
}

current_process_in_playit_group() {
  id -nG 2>/dev/null | tr ' ' '\n' | grep -qx playit
}

ensure_playit_group_membership() {
  local target_user

  if [ "${YES}" -eq 1 ]; then
    return
  fi

  if ! target_user=$(playit_target_user); then
    echo "Running as root without a non-root invoking user; skipping playit group membership update."
    return
  fi

  if ! playit_group_exists; then
    echo "The playit group was not found. Open a new shell and run: playit"
    return
  fi

  if user_in_playit_group "${target_user}"; then
    return
  fi

  if ! has_cmd usermod; then
    echo "The usermod command was not found. Add ${target_user} to the playit group before running playit."
    return
  fi

  run_step "Add ${target_user} to the playit group." "${SUDO[@]}" usermod -aG playit "${target_user}"
}

run_playit_with_group() {
  if current_process_in_playit_group; then
    if tty_available; then
      playit </dev/tty
    else
      playit
    fi
    return
  fi

  if has_cmd sg; then
    if tty_available; then
      sg playit -c playit </dev/tty
    else
      sg playit -c playit
    fi
    return
  fi

  echo "The playit group update is installed, but this shell cannot use it yet."
  echo "Open a new shell or run: newgrp playit"
  echo "Then start playit with: playit"
}

maybe_start_playit() {
  if [ "${YES}" -eq 1 ]; then
    return
  fi

  ensure_playit_group_membership

  if ask_yes_no "Would you like to start playit now to finish setup?" yes; then
    if has_cmd playit; then
      run_playit_with_group
    else
      echo "playit was installed, but the playit command was not found in PATH. Open a new shell and run: playit"
    fi
  fi
}

main() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      -y|--yes)
        YES=1
        ;;
      --override-package-url)
        if [ -z "${2:-}" ]; then
          echo "missing value for --override-package-url" >&2
          exit 1
        fi
        PACKAGE_BASE_URL="$2"
        case "${PACKAGE_BASE_URL}" in
          http://*|https://*) ;;
          *)
            echo "--override-package-url must start with http:// or https://" >&2
            exit 1
            ;;
        esac
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        usage >&2
        exit 1
        ;;
    esac
    shift
  done

  configure_package_urls
  detect_package_manager

  case "${PKG_MANAGER}" in
    apt-get) install_debian ;;
    dnf) install_fedora_like dnf ;;
    yum) install_fedora_like yum ;;
    zypper) install_opensuse ;;
    apk) install_alpine ;;
    pacman) install_arch ;;
  esac

  echo
  echo "playit installation finished."
  maybe_start_playit
}

if [ "${INSTALL_SH_SOURCE_ONLY:-0}" -ne 1 ]; then
  main "$@"
fi
