#!/bin/bash
#/ Usage: bin/strap.sh [options]
#/ Install development dependencies on macOS.
set -e

STRAP_INTERACTIVE=""
if test -t 0 && [ "${CI:-0}" != "1" ] && [ -z "$STRAP_CI$STRAP_NONINTERACTIVE" ]; then
  STRAP_INTERACTIVE="1"
fi

while [ "$#" -gt 0 ]; do
  case "$1" in
  --debug) STRAP_DEBUG="1" ;;
  --non-interactive) STRAP_INTERACTIVE="" ;;
  --help | -h)
    cat <<'HELP'
Usage: strap.sh [--debug] [--non-interactive]
  --debug             Enable debugging output (STRAP_DEBUG=1).
  --non-interactive   Do not prompt (STRAP_NONINTERACTIVE=1).
  --help              Show this help.

Set STRAP_GIT_NAME, STRAP_GIT_EMAIL and STRAP_GITHUB_USER in the environment
for Git configuration and the dotfiles/Brewfile owner. Existing Git name
and email are preserved unless explicitly overridden.
Non-interactive mode is automatic without a TTY or with CI=1.
GitHub login is optional. Non-interactive runs can reuse an existing login
or credentials supplied through GH_TOKEN or GITHUB_TOKEN.
HELP
    exit 0
    ;;
  *)
    echo "!!! Unknown option: $1. Run '$0 --help' for usage." >&2
    exit 1
    ;;
  esac
  shift
done
if [ -z "$STRAP_INTERACTIVE" ]; then
  export NONINTERACTIVE=1 GIT_TERMINAL_PROMPT=0
fi
[[ -o xtrace ]] && STRAP_DEBUG="1"
STRAP_SUCCESS=""

sudo_askpass() {
  if [ -z "$STRAP_INTERACTIVE" ]; then
    sudo --non-interactive "$@"
  elif [ -n "$SUDO_ASKPASS" ]; then
    sudo --askpass "$@"
  else
    sudo "$@"
  fi
}

cleanup() {
  set +e
  if [ -n "$CLT_PLACEHOLDER$SUDO_ASKPASS$SUDO_ASKPASS_DIR" ]; then
    sudo_askpass rm -rf "$CLT_PLACEHOLDER" "$SUDO_ASKPASS" "$SUDO_ASKPASS_DIR"
  fi
  sudo --reset-timestamp
  if [ -z "$STRAP_SUCCESS" ]; then
    if [ -n "$STRAP_STEP" ]; then
      echo "!!! $STRAP_STEP FAILED" >&2
    else
      echo "!!! FAILED" >&2
    fi
    if [ -z "$STRAP_DEBUG" ]; then
      echo "!!! Run '$0 --debug' for debugging output." >&2
    fi
  fi
}

trap "cleanup" EXIT

if [ -n "$STRAP_DEBUG" ]; then
  set -x
else
  STRAP_QUIET_FLAG="-q"
  Q="$STRAP_QUIET_FLAG"
fi

# Interactive runs should prompt for sudo at least once.
if [ -n "$STRAP_INTERACTIVE" ]; then
  sudo --reset-timestamp
fi

# functions for turning off debug for use when handling the user password
clear_debug() {
  set +x
}

reset_debug() {
  if [ -n "$STRAP_DEBUG" ]; then
    set -x
  fi
}

# Initialise (or reinitialise) sudo to save unhelpful prompts later.
sudo_init() {
  if [ -z "$STRAP_INTERACTIVE" ]; then
    return
  fi

  # If TouchID for sudo is setup: use that instead.
  if grep -q pam_tid /etc/pam.d/sudo /etc/pam.d/sudo_local 2>/dev/null; then
    return
  fi

  local SUDO_PASSWORD SUDO_PASSWORD_SCRIPT

  if ! sudo --validate --non-interactive &>/dev/null; then
    while true; do
      read -rsp "--> Enter your password (for sudo access):" SUDO_PASSWORD
      echo
      if sudo --validate --stdin 2>/dev/null <<<"$SUDO_PASSWORD"; then
        break
      fi

      unset SUDO_PASSWORD
      echo "!!! Wrong password!" >&2
    done

    clear_debug
    SUDO_PASSWORD_SCRIPT="$(
      cat <<BASH
#!/bin/bash
echo "$SUDO_PASSWORD"
BASH
    )"
    unset SUDO_PASSWORD
    SUDO_ASKPASS_DIR="$(mktemp -d)"
    SUDO_ASKPASS="$(mktemp "$SUDO_ASKPASS_DIR"/strap-askpass-XXXXXXXX)"
    chmod 700 "$SUDO_ASKPASS_DIR" "$SUDO_ASKPASS"
    bash -c "cat > '$SUDO_ASKPASS'" <<<"$SUDO_PASSWORD_SCRIPT"
    unset SUDO_PASSWORD_SCRIPT
    reset_debug

    export SUDO_ASKPASS
  fi
}

sudo_refresh() {
  if [ -z "$STRAP_INTERACTIVE" ]; then
    return
  fi
  clear_debug
  if [ -n "$SUDO_ASKPASS" ]; then
    sudo --askpass --validate
  else
    sudo_init
  fi
  reset_debug
}

abort() {
  STRAP_STEP=""
  echo "!!! $*" >&2
  exit 1
}

log() {
  STRAP_STEP="$*"
  echo "--> $*"
  sudo_refresh
}

logn() {
  STRAP_STEP="$*"
  printf -- "--> %s " "$*"
  sudo_refresh
}

logk() {
  STRAP_STEP=""
  echo "OK"
}

logskip() {
  STRAP_STEP=""
  echo "SKIPPED"
  echo "$*"
}

defaults_write() {
  if [ "$(defaults read "$1" "$2" 2>/dev/null)" = "${!#}" ]; then
    return
  fi
  if [[ $1 == /* ]]; then
    sudo_askpass defaults write "$@"
  else
    defaults write "$@"
  fi
}

# Given a list of scripts in the dotfiles repo, run the first one that exists
run_dotfile_scripts() {
  if [ -d ~/.dotfiles ]; then
    (
      cd ~/.dotfiles
      for i in "$@"; do
        if [ -f "$i" ] && [ -x "$i" ]; then
          log "Running dotfiles $i:"
          "$i"
          logk
          break
        fi
      done
    )
  fi
}

[ "$USER" = "root" ] && abort "Run Strap as yourself, not root."
groups | grep $Q -E "\b(admin)\b" || abort "Add $USER to the admin group."

# Prevent sleeping during script execution, as long as the machine is on AC power
caffeinate -s -w $$ &

# Check and, if necessary, enable sudo authentication using TouchID.
# Don't care about non-alphanumeric filenames when doing a specific match
# shellcheck disable=SC2010
if ls /usr/lib/pam | grep $Q "pam_tid.so"; then
  logn "Configuring sudo authentication using TouchID:"
  if [[ -f /etc/pam.d/sudo_local || -f /etc/pam.d/sudo_local.template ]]; then
    # New in macOS Sonoma, survives updates.
    PAM_FILE="/etc/pam.d/sudo_local"
    FIRST_LINE="# sudo_local: local config file which survives system update and is included for sudo"
    if [[ ! -f "/etc/pam.d/sudo_local" ]]; then
      echo "$FIRST_LINE" | sudo_askpass tee "$PAM_FILE" >/dev/null
    fi
  else
    PAM_FILE="/etc/pam.d/sudo"
    FIRST_LINE="# sudo: auth account password session"
  fi
  if grep $Q pam_tid.so "$PAM_FILE"; then
    logk
  elif ! head -n1 "$PAM_FILE" | grep $Q "$FIRST_LINE"; then
    logskip "$PAM_FILE is not in the expected format!"
  else
    TOUCHID_LINE="auth       sufficient     pam_tid.so"
    sudo_askpass sed -i .bak -e \
      "s/$FIRST_LINE/$FIRST_LINE\n$TOUCHID_LINE/" \
      "$PAM_FILE"
    sudo_askpass rm "$PAM_FILE.bak"
    logk
  fi
fi

# Set some basic security settings.
logn "Configuring security settings:"
defaults_write com.apple.screensaver askForPassword -int 1
defaults_write com.apple.screensaver askForPasswordDelay -int 0
defaults_write /Library/Preferences/com.apple.alf globalstate -int 1
if ! launchctl print system/com.apple.alf &>/dev/null; then
  sudo_askpass launchctl load /System/Library/LaunchDaemons/com.apple.alf.agent.plist 2>/dev/null
fi

logk

# Check and enable full-disk encryption.
logn "Checking full-disk encryption status:"
if fdesetup status | grep $Q -E "FileVault is (On|Off, but will be enabled after the next restart)."; then
  logk
elif [ -n "$STRAP_CI" ]; then
  echo "SKIPPED (for CI)"
elif [ -n "$STRAP_INTERACTIVE" ]; then
  echo
  log "Enabling full-disk encryption on next reboot:"
  sudo_askpass fdesetup enable -user "$USER" |
    tee ~/Desktop/"FileVault Recovery Key.txt"
  logk
else
  echo
  abort "Run 'sudo fdesetup enable -user \"$USER\"' to enable full-disk encryption."
fi

# Install the Xcode Command Line Tools.
if ! [ -f "/Library/Developer/CommandLineTools/usr/bin/git" ]; then
  log "Installing the Xcode Command Line Tools:"
  CLT_PLACEHOLDER="/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress"
  sudo_askpass touch "$CLT_PLACEHOLDER"

  for CLT_RETRY_DELAY in 0 10 20; do
    sleep "$CLT_RETRY_DELAY"
    CLT_PACKAGE=$(softwareupdate -l |
      tee /dev/stderr |
      grep -B 1 "Command Line Tools" |
      awk -F"*" '/^ *\*/ {print $2}' |
      sed -e 's/^ *Label: //' -e 's/^ *//' |
      sort -V |
      tail -n1)
    if [ -n "$CLT_PACKAGE" ]; then
      sudo_askpass softwareupdate -i "$CLT_PACKAGE"
      break
    fi
  done
  sudo_askpass rm -f "$CLT_PLACEHOLDER"
  if ! [ -f "/Library/Developer/CommandLineTools/usr/bin/git" ]; then
    if [ -n "$STRAP_INTERACTIVE" ]; then
      echo
      logn "Requesting user install of Xcode Command Line Tools:"
      xcode-select --install
    else
      echo
      abort "Run 'xcode-select --install' to install the Xcode Command Line Tools."
    fi
  fi
  logk
fi

# Check if the Xcode license is agreed to and agree if not.
xcode_license() {
  if /usr/bin/xcrun clang 2>&1 | grep $Q license; then
    if [ -n "$STRAP_INTERACTIVE" ]; then
      logn "Asking for Xcode license confirmation:"
      sudo_askpass xcodebuild -license
      logk
    else
      abort "Run 'sudo xcodebuild -license' to agree to the Xcode license."
    fi
  fi
}
xcode_license

# Setup Homebrew directory and permissions.
if [[ "$(uname -m)" == "arm64" ]]; then
  HOMEBREW_PREFIX="/opt/homebrew"
  HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}"
  export PATH="/opt/homebrew/bin:$PATH"
else
  HOMEBREW_PREFIX="/usr/local"
  HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew"
  export PATH="/usr/local/bin:$PATH"
fi

if ! command -v brew &>/dev/null; then
  logn "Installing Homebrew:"
  [ -d "$HOMEBREW_PREFIX" ] || sudo_askpass mkdir -p "$HOMEBREW_PREFIX"
  if [ "$HOMEBREW_PREFIX" = "/usr/local" ]; then
    sudo_askpass chown "root:wheel" "$HOMEBREW_PREFIX" 2>/dev/null || true
  fi
  (
    cd "$HOMEBREW_PREFIX"
    sudo_askpass mkdir -p Cellar Caskroom Frameworks bin etc include lib opt sbin share var
    sudo_askpass chown "$USER:admin" Cellar Caskroom Frameworks bin etc include lib opt sbin share var
  )

  [ -d "$HOMEBREW_REPOSITORY" ] || sudo_askpass mkdir -p "$HOMEBREW_REPOSITORY"
  sudo_askpass chown -R "$USER:admin" "$HOMEBREW_REPOSITORY"

  if [ $HOMEBREW_PREFIX != $HOMEBREW_REPOSITORY ]; then
    ln -sf "$HOMEBREW_REPOSITORY/bin/brew" "$HOMEBREW_PREFIX/bin/brew"
  fi

  # Download Homebrew.
  export GIT_DIR="$HOMEBREW_REPOSITORY/.git" GIT_WORK_TREE="$HOMEBREW_REPOSITORY"
  git init $Q
  git config remote.origin.url "https://github.com/Homebrew/brew"
  git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
  git fetch $Q --tags --force
  git reset $Q --hard origin/master
  unset GIT_DIR GIT_WORK_TREE
  logk
fi

# Update Homebrew.
log "Checking Homebrew is up to date:"
brew update --quiet
logk

# Set up GitHub before accessing dotfiles or Brewfiles.
if [ -z "$STRAP_GIT_NAME" ]; then
  STRAP_GIT_NAME=$(git config user.name) || unset STRAP_GIT_NAME
fi
if [ -z "$STRAP_GIT_EMAIL" ]; then
  STRAP_GIT_EMAIL=$(git config user.email) || unset STRAP_GIT_EMAIL
fi

STRAP_GITHUB_AUTHENTICATED=""
if [ -z "$STRAP_CI" ]; then
  if command -v gh &>/dev/null && gh auth status --active --hostname github.com &>/dev/null; then
    STRAP_GITHUB_AUTHENTICATED="1"
  else
    STRAP_GITHUB_SETUP=""
    if [ -n "${GH_TOKEN:+1}" ] || [ -n "${GITHUB_TOKEN:+1}" ]; then
      STRAP_GITHUB_SETUP="1"
    elif [ -n "$STRAP_INTERACTIVE" ]; then
      read -rp "--> Log in to GitHub to configure Git and fetch your dotfiles? [Y/n] " STRAP_GITHUB_SETUP || STRAP_GITHUB_SETUP="n"
      case "$STRAP_GITHUB_SETUP" in
      "" | y | Y | yes | YES) STRAP_GITHUB_SETUP="1" ;;
      *) STRAP_GITHUB_SETUP="" ;;
      esac
    fi

    if [ -n "$STRAP_GITHUB_SETUP" ]; then
      if ! command -v gh &>/dev/null; then
        log "Installing GitHub CLI:"
        brew install gh
      fi
      if [ -n "${GH_TOKEN:+1}" ] || [ -n "${GITHUB_TOKEN:+1}" ]; then
        if gh auth status --active --hostname github.com &>/dev/null; then
          STRAP_GITHUB_AUTHENTICATED="1"
        fi
      else
        STRAP_GITHUB_LOGIN_ARGS=(--hostname github.com --git-protocol https --web)
        if [ -z "$STRAP_GIT_EMAIL" ]; then
          STRAP_GITHUB_LOGIN_ARGS+=(--scopes user:email)
        fi
        if gh auth login "${STRAP_GITHUB_LOGIN_ARGS[@]}"; then
          STRAP_GITHUB_AUTHENTICATED="1"
        fi
      fi
      if [ -z "$STRAP_GITHUB_AUTHENTICATED" ]; then
        logskip "GitHub authentication failed; continuing without GitHub profile lookup."
      fi
    fi
  fi
fi

if [ -n "$STRAP_GITHUB_AUTHENTICATED" ]; then
  if ! GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/usr/bin/false \
    git -c credential.interactive=false credential fill <<<"url=https://github.com" &>/dev/null; then
    gh auth setup-git --hostname github.com
  fi
  if [ -z "$STRAP_GITHUB_USER" ]; then
    STRAP_GITHUB_USER=$(gh api user --hostname github.com --jq .login) || true
  fi
  if [ -z "$STRAP_GIT_NAME" ]; then
    STRAP_GIT_NAME=$(gh api user --hostname github.com --jq '.name // empty') || true
  fi
  if [ -z "$STRAP_GIT_EMAIL" ]; then
    STRAP_GIT_EMAIL=$(gh api user/emails --hostname github.com --paginate --jq '.[] | select(.primary and .verified) | .email') ||
      logskip "Set STRAP_GIT_EMAIL or grant email access with 'gh auth refresh --hostname github.com --scopes user:email'."
  fi
fi

# Setup Git configuration.
logn "Configuring Git:"
if [ -n "$STRAP_GIT_NAME" ] && [ "$(git config user.name)" != "$STRAP_GIT_NAME" ]; then
  git config --global user.name "$STRAP_GIT_NAME"
fi
if [ -n "$STRAP_GIT_EMAIL" ] && [ "$(git config user.email)" != "$STRAP_GIT_EMAIL" ]; then
  git config --global user.email "$STRAP_GIT_EMAIL"
fi
if [ -n "$STRAP_GITHUB_USER" ] && [ "$(git config github.user)" != "$STRAP_GITHUB_USER" ]; then
  git config --global github.user "$STRAP_GITHUB_USER"
fi
logk

# Check and install any remaining software updates.
logn "Checking for software updates:"
if softwareupdate -l 2>&1 | grep $Q "No new software available."; then
  logk
else
  echo
  log "Installing software updates:"
  if [ -z "$STRAP_CI" ] && [ -z "$STRAP_NO_SOFTWAREUPDATE" ]; then
    sudo_askpass softwareupdate --install --all
    xcode_license
    logk
  elif [ -n "$STRAP_CI" ]; then
    echo "SKIPPED (for CI)"
  else
    echo "SKIPPED (by STRAP_NO_SOFTWAREUPDATE)"
  fi
fi

if [ -n "$STRAP_GIT_NAME" ] && [ -n "$STRAP_GIT_EMAIL" ]; then
  LOGIN_TEXT="Found this computer? Please contact $STRAP_GIT_NAME at $STRAP_GIT_EMAIL."
  if [ "$(defaults read /Library/Preferences/com.apple.loginwindow LoginwindowText 2>/dev/null)" != "$LOGIN_TEXT" ]; then
    logn "Configuring login-screen message:"
    sudo_askpass defaults write /Library/Preferences/com.apple.loginwindow LoginwindowText -string "$LOGIN_TEXT"
    logk
  fi
fi

# Setup dotfiles
if [ -n "$STRAP_GITHUB_USER" ]; then
  DOTFILES_URL="https://github.com/$STRAP_GITHUB_USER/dotfiles"

  if GIT_TERMINAL_PROMPT=0 git ls-remote "$DOTFILES_URL" &>/dev/null; then
    log "Fetching $STRAP_GITHUB_USER/dotfiles from GitHub:"
    if [ ! -d "$HOME/.dotfiles" ]; then
      log "Cloning to ~/.dotfiles:"
      git clone $Q "$DOTFILES_URL" ~/.dotfiles
    else
      log "Updating ~/.dotfiles:"
      git -C ~/.dotfiles pull $Q --rebase --autostash
    fi
    logk
    run_dotfile_scripts script/setup script/bootstrap
  fi
fi

# Setup Brewfile
if [ -n "$STRAP_GITHUB_USER" ] && { [ ! -f "$HOME/.Brewfile" ] || [ "$HOME/.Brewfile" -ef "$HOME/.homebrew-brewfile/Brewfile" ]; }; then
  HOMEBREW_BREWFILE_URL="https://github.com/$STRAP_GITHUB_USER/homebrew-brewfile"

  if GIT_TERMINAL_PROMPT=0 git ls-remote "$HOMEBREW_BREWFILE_URL" &>/dev/null; then
    log "Fetching $STRAP_GITHUB_USER/homebrew-brewfile from GitHub:"
    if [ ! -d "$HOME/.homebrew-brewfile" ]; then
      log "Cloning to ~/.homebrew-brewfile:"
      git clone $Q "$HOMEBREW_BREWFILE_URL" ~/.homebrew-brewfile
    else
      log "Updating ~/.homebrew-brewfile:"
      git -C ~/.homebrew-brewfile pull $Q
    fi
    ln -sf ~/.homebrew-brewfile/Brewfile ~/.Brewfile
    logk
  fi
fi

# Install from local Brewfile
if [ -f "$HOME/.Brewfile" ]; then
  log "Checking ~/.Brewfile dependencies:"
  brew bundle check --global &>/dev/null || brew bundle --global $Q
  logk
fi

# Run post-install dotfiles script
run_dotfile_scripts script/strap-after-setup

STRAP_SUCCESS="1"
log "Your system is now Strap'd!"
