#!/usr/bin/env bash

set -o pipefail

WHEREAMI="$(dirname "$(readlink -f "$0")")"

# ------------------------- Load Package Specific Data -------------------------

# shellcheck source=./locals
. "${WHEREAMI}/locals"

# -------------------------- Installation Constants ----------------------------

## Root of the package directory where this script is located
PKG_ROOT="$(dirname "${WHEREAMI}")"

## Whether to respond yes to all questions for the user during install
NO_PROMPT=false

## Imply service user name
IMPLY_USER="imply"

## Imply service group name
IMPLY_GROUP="imply"

## Installation abort message 
ABRT_MSG="\e[1m${PKG_NAME} Install\e[0m \e[93mAborted...\e[0m"

## Installation failed message 
FAILED_MSG="\e[1m${PKG_NAME} Install\e[0m \e[31mFailed!\e[0m"

## Installation succeeded message 
SUCCEEDED_MSG="\e[1m${PKG_NAME} Install\e[0m \e[92mSucceeded!\e[0m"

# ----------------------------- Helper Functions -------------------------------

## Prints an error message to stderr and aborts the script
#
# Usage:
#   $ abort "Error occurred. Aborting..." 

abort()
{
  echo -e 1>&2 "$1"
  exit 1
}

## Logs and error and exits if the previously run command failed
#
# Usage:
#   $ chk_ret "Failure!"

chk_ret()
{
  if [[ "$?" -ne 0 ]]; then 
    echo -e 1>&2 "$1"
    exit 1
  fi
}

## Ask user a yes or no question, loop if they provide invalid input 
#
# Usage:
#   $ ask_yes_no "Do something?" 
#   Do something [Y/n]:

ask_yes_no()
{
  while true; do
      read -rp "$1 [Y/n]: " yn
      case ${yn} in
          [Yy]* ) return 0;;
          [Nn]* ) return 1;;
          * ) echo "Please answer yes or no.";;
      esac
  done
}

## Checks if a user can read and write a file
#
# Usage:
#   $ can_rw_file imply /some/file

can_rw_file()
{
  local -r user="$1"
  local -r file="$2"

  su "${user}" -s /bin/bash -c "[[ -r ${file} && -w ${file} ]]"
}

## Checks if file ends with .service then replaces User and Group in systemd unit
#
# Usage:
#   $ install_systemd_unit /some/file user group

install_systemd_unit()
{
  local -r file="$1"
  local -r user="$2"
  local -r group="$3"

  if ! [[ "${file}" =~ \.service$ ]]; then
    return 1;
  fi

  sed -i -e "s/\@{IMPLY_USER}/${user}/g" \
    -e "s/@{IMPLY_GROUP}/${group}/g" \
    "${file}"
}

## Checks if group exists and returns its group id
#
# Usage:
#   $ get_gid group

get_gid()
{
  local -r group="$1"

  if ! [ $(getent group "${group}") ]; then
    return 1
  fi

  cut -d: -f3 < <(getent group "${group}")
}

resolve_multi_arch_deps()
{
  for dep in "${MULTI_ARCH_DEPS[@]}"; do
    IFS=, read -r TARGET_DIR SOURCE_DIR <<< "$dep"
    cp "${PKG_ROOT}${SOURCE_DIR}"/"$(uname -m)"/* "${PKG_ROOT}${TARGET_DIR}/"
  done
}

# ------------------------------ Get User Options ------------------------------

function usage() {
  echo -e "Usage: ${PKG_NAME} Installer [-u user] [-g group] [-y] [-h]"
  exit 1
}

while getopts "u:g:yh" opt; do
  case "${opt}" in
    y)
      NO_PROMPT=true
      ;;
    u)
      IMPLY_USER="${OPTARG}"
      ;;
    g)
      IMPLY_GROUP="${OPTARG}"
      ;;
    h | *)
      usage
  esac
done

shift $(( OPTIND - 1 ))

# -------------------------------- Ensure Linux --------------------------------

if [[ $(uname -o) != "GNU/Linux" ]]; then
  abort "Unsupported OS [$(uname -o)].\n${ABRT_MSG}"
fi

# -------------------------------- Ensure Root ---------------------------------

if [[ $(id -u) -ne 0 ]]; then
  abort "Installer must be run under the root user.\n${ABRT_MSG}"
fi

# ----------------------------- Ensure Dependencies ----------------------------

for DEP in "${DEPENDENCIES[@]}"; do
  message="${PKG_NAME} dependency [${DEP}] not found. Please install"
  message="$message and try again.\n${ABRT_MSG}"

  command -v "${DEP}" >/dev/null 2>&1 ||
  abort "${message}"
done

unset message

# -------------------------- Ensure SystemD is Running -------------------------

# Note: The sd_booted() system call, which checks whether the system was booted
#       using the systemd init system uses the following method.
#       https://www.freedesktop.org/software/systemd/man/sd_booted.html 

if [[ ! -d "/run/systemd/system" ]]; then
  abort "SystemD is not running on this machine.\n${ABRT_MSG}"
fi

# ------------------------- Ensure Imply is Not Running -------------------------

# Note: This is a weak check that simply determines if there is an existing
#       running copy of Imply Manager installed through this script or any of
#       its predecessors.

if systemctl status "${IMPLY_SERVICES[@]}" >/dev/null 2>&1; then
  abort "A version of ${PKG_NAME} is currently running.\n${ABRT_MSG}"
fi

# ----------------------- Maybe Create Imply User & Group ----------------------

## Ensure Imply user is created
if ! id -u "${IMPLY_USER}" >/dev/null 2>&1; then
  useradd -c 'Imply' -M -s /usr/sbin/nologin -r "${IMPLY_USER}" &> /dev/null
  chk_ret "${FAILED_MSG}"
fi

## Ensure Imply group is created
if ! [ $(get_gid "${IMPLY_GROUP}") ]; then
  groupadd -r "${IMPLY_GROUP}" &> /dev/null
  chk_ret "${FAILED_MSG}"
fi

## Ensure Imply user is part of the Imply group
if ! id -Gn "${IMPLY_USER}" | grep -qw "${IMPLY_GROUP}"; then
  usermod -a -G "${IMPLY_GROUP}" "${IMPLY_USER}" &> /dev/null
  chk_ret "${FAILED_MSG}"
fi

## Ensure Imply user does not have uid 0 (root)
if [[ $(id -u "${IMPLY_USER}") -eq 0 ]]; then
  abort "The user [${IMPLY_USER}] must not have uid=0.\n${ABRT_MSG}"
fi

## Ensure Imply user does not have uid 0 (root)
if [[ $(get_gid "${IMPLY_GROUP}") -eq 0 ]]; then
  abort "The group [${IMPLY_GROUP}] must not have gid=0.\n${ABRT_MSG}"
fi

# ------------------ Ensure Imply can r+w External Directories -----------------

for EXT_DIR in "${EXTERNAL_DIRS[@]}"; do
  if [[ -d "${EXT_DIR}" ]] && ! can_rw_file "${IMPLY_USER}" "${EXT_DIR}"; then
    message="The user [${IMPLY_USER}] must have r+w permissions for"
    message="$message directory [${EXT_DIR}]\n${ABRT_MSG}"
    abort "${message}"
  fi
done

unset message

# --------------------- Maybe Remove Existing Installation ---------------------

if [[ -d "/opt/${PROV_PKG}" || -d "/etc/opt/${PROV_PKG}" || -d "/var/opt/${PROV_PKG}" ]]; then
  message="A version of ${PKG_NAME} is already installed.  Remove it?"
  if "${NO_PROMPT}" || ask_yes_no "${message}"; then
    rm -rf "/opt/${PROV_PKG}" "/etc/opt/${PROV_PKG}" "/var/opt/${PROV_PKG}"
    chk_ret "${FAILED_MSG}"
  else
    abort "${ABRT_MSG}"
  fi
fi 

# ---------------------------- Install Package Files ---------------------------

echo -e "\e[1mInstalling\e[0m ..."

# Ensure the correct architecture specific binaries are in the correct location
resolve_multi_arch_deps

## Collect package files under the directories and subdirectories of the
#  package root except for files within the script directory

EXCLUDE_PATHS=("${PKG_ROOT}/script")

for dep in "${MULTI_ARCH_DEPS[@]}"; do
  IFS=',' read -r TARGET_DIR SOURCE_DIR <<< "$dep"
  EXCLUDE_PATHS+=("${PKG_ROOT}${SOURCE_DIR}")
done

# Construct the find command with excluded paths
FIND_CMD="find ${PKG_ROOT}"

for EXCLUDE_PATH in "${EXCLUDE_PATHS[@]}"; do
  FIND_CMD+=" -path '${EXCLUDE_PATH}' -prune -o"
done

# Add the final action to find files
FIND_CMD+=" -type f -print"

# Construct and execute the final find command
PKG_FILES=$(eval "${FIND_CMD}")

## Package files are located at the same path as their intended install
#  location underneath the package root.
#
#  Example
#    PKG_ROOT=/path/to/imply-{pkg}
#    PKG_FILE=/path/to/imply-{pkg}/opt/imply/bin/runme
#    INSTALL_PATH=/opt/imply/bin/runme
#    INSTALL_DIR=/opt/imply//bin/

for PKG_FILE in ${PKG_FILES[*]}; do
  INSTALL_PATH=${PKG_FILE##"${PKG_ROOT}"}
  INSTALL_DIR=$(dirname "${INSTALL_PATH}")
  
  # Ensure install directory exists
  mkdir -p "${INSTALL_DIR}"
  chk_ret "${FAILED_MSG}"

  # Install package file 
  cp "${PKG_FILE}" "${INSTALL_PATH}" 
  chk_ret "${FAILED_MSG}"

  if [[ "${PKG_FILE}" =~ \.service$ ]]; then
    install_systemd_unit "${INSTALL_PATH}" "${IMPLY_USER}" ""${IMPLY_GROUP}""
    chk_ret "${FAILED_MSG}"
  fi
done

# ---------------------- Create Configuration Directories ----------------------

mkdir -p "/etc/opt/${PROV_PKG}"
chk_ret "${FAILED_MSG}"

# ---------------------- Maybe Create External Directories ---------------------

for EXT_DIR in "${EXTERNAL_DIRS[@]}"; do
  if [[ ! -d "${EXT_DIR}" ]]; then
    mkdir -p "${EXT_DIR}"
    chk_ret "${FAILED_MSG}"

    chown -R "${IMPLY_USER}":"${IMPLY_GROUP}" "${EXT_DIR}"
    chk_ret "${FAILED_MSG}"
  fi
done

# ------------------------- Create Runtime Directories -------------------------

mkdir -p "${RUNTIME_SUBDIRS[@]}"
chk_ret "${FAILED_MSG}"

chown -R "${IMPLY_USER}":"${IMPLY_GROUP}" "${RUNTIME_SUBDIRS[@]}"
chk_ret "${FAILED_MSG}"

# -------------------- Assign Top Level Directories to Imply -------------------

chown -R "${IMPLY_USER}":"${IMPLY_GROUP}" /opt/imply /var/opt/imply /etc/opt/imply
chk_ret "${FAILED_MSG}"

# --------------------------- Enable SystemD Services --------------------------

SERVICE_FILES=$(find "${PKG_ROOT}" -path "*systemd*" -type f -name "*.service")
for SERVICE_FILE in ${SERVICE_FILES[*]}; do
  systemctl enable "$(basename "${SERVICE_FILE}")" >/dev/null 2>&1
  chk_ret "${FAILED_MSG}"
done

echo -e "${SUCCEEDED_MSG}"
