#!/bin/bash

set -e

declare -r c_dest="/etc/apt/sources.list.d/sipwise_ppa.list"
declare -r c_pref="/etc/apt/preferences.d/00_sipwise_ppa"
declare -r c_me="$0"
list=("$@")
sudo="sudo"
clean=false
force=false
approx_all=false
approx_ppa=false
update=false
upgrade=false
ngcp_update=false
apt_ppa_pin="1001"
ngcp_version=unknown
carrier_edition=false
pro_edition=false
ce_edition=false
devel_mode=false
ngcpcfg=true

help() {
  echo "Usage: ${c_me} [OPTIONS] ppa_repo_1 ppa_repo_2 ppa_repo_3 ..."
  echo
  echo "Add/register Sipwise PPA repository as NGCP/Debian APT repository and"
  echo "update approx cache, apt metadata, debian packages (if requested)."
  echo
  echo "  Supported options :"
  echo "     -h|--help      : show this help"
  echo "     -c|--clean     : clean all current PPA configuration"
  echo "     -f|--force     : force add all PPA repositories, duplicates will be skipped"
  echo "     -d|--update    : run 'aptitude update' at the end of script"
  echo "     -g|--upgrade   : run 'aptitude update + upgrade' at the end of the script"
  echo "     -P|--approx-ppa: run 'approx PPA + aptitude update + upgrade' (conflicts with --approx-all)"
  echo "     -A|--approx-all: run 'approx ALL + aptitude update + upgrade' (conflicts with --approx-ppa)"
  echo "     -k|--keep-devel: skip approx usage + run 'aptitude update + upgrade' (devels behaviour)"
  echo "     -n|--no-sudo   : run all the commands without 'sudo'"
  echo "     -p|--pin <pin> : set APT preferences PIN value for PPA repository (default 1001)"
  echo "        --url <url> : Use specified url for PPA repository"
  echo "  --without-ngcpcfg : add PPA directly to apt conf files instead of using ngcpcfg"
  echo
  echo " Deprecated options :"
  echo "      --skip-search : don't search, use the script options as PPA repositories"
  echo "   -l|--legacy      : run ngcp-ppa in legacy mode (APT PIN for PPA repos is 991)"
  echo "   -u|--user <name> : search PPA repositories for specified user only"
}

configure() {
  declare -a _list=("$@")

  if [[ -z "${_list[*]}" ]]; then
    echo "Error: search criteria is not specified, see the help below. Aborted!" >&2
    echo
    help
    exit 1
  fi

  local _active_opts="help,clean,force,update,upgrade,approx-ppa,approx-all,no-sudo,keep-devel,pin:,url:,without-ngcpcfg"
  local _deprecated_opts="skip-search,legacy,user:"
  local _cmdline_opts="${_active_opts},${_deprecated_opts}"

  local _opt_temp=
  _opt_temp=$(getopt -n "${c_me}" -o "+hckfdgPAnlkp:u:" -l "${_cmdline_opts}" -- "${_list[@]}")
  eval set -- "${_opt_temp}"

  while :; do
    case "$1" in
    --help|-h)
      help; exit 0
      ;;
    --clean|-c)
      clean=true
      ;;
    --force|-f)
      force=true
      ;;
    --update|-d)
      update=true
      ;;
    --upgrade|-g)
      update=true
      upgrade=true
      ;;
    --approx-ppa|-P)
      update=true
      upgrade=true
      approx_ppa=true
      ;;
    --approx-all|-A)
      update=true
      upgrade=true
      approx_all=true
      ;;
    --no-sudo|-n)
      sudo=""
      ;;
    --user|-u)
      echo "Error: the script option '--user|-u' is deprecated." >&2
      echo "This option will be removed soon." >&2
      exit 1
      ;;
    --legacy|-l)
      echo "The script option '--legacy|-l' is deprecated, please use '--pin=991'" >&2
      echo "The option '--legacy|-l' will be removed soon." >&2
      exit 1
      ;;
    --pin|-p)
      shift; apt_ppa_pin="$1"
      ;;
    --keep-devel|-k)
      devel_mode=true
      ngcpcfg=false
      ;;
    --skip-search)
      echo "The script option '--skip-search' is deprecated, just remove it." >&2
      echo "The option '--skip-search' will be removed soon." >&2
      exit 1
      ;;
    --url)
      shift; ppa_url="${1}"
      ;;
    --without-ngcpcfg)
      ngcpcfg=false
      ;;
    --)
      shift; break
      ;;
    *)
      echo "Internal getopt error! $1" >&2
      exit 1
      ;;
    esac
    shift
  done

  list=("$@")
}

check_env() {
  declare -a _list=("$@")

  if [[ "${_list[*]}" = "" && ! ${clean} ]]; then
    help
    exit 1
  fi

  if [[ "${ngcp_version}" != "trunk" ]]; then
    for ppa in "${_list[@]}"; do
      if [[ ! $ppa =~ ^ppa-.*/mr.*$ ]]; then
        continue
      fi

      local rel="${ppa##*/}"
      local br="${rel%.*}"

      if [[ "${ngcp_version}" != "${rel}" && "${ngcp_version}" != "${br}" ]]; then
        echo "Warning: PPA ${ppa} release does not match system release ${ngcp_version}." >&2
        echo >&2
      fi
    done
  fi

  if "${approx_ppa}" && "${approx_all}"; then
    echo "ERROR: options 'approx-ppa' and 'approx-all' cannot be used simultaneously." >&2
    help
    exit 1
  fi

  local ngcpcfg_diff
  if "${ngcpcfg}"; then
    ngcpcfg_diff="$(ngcpcfg diff)"
    if [[ -n "${ngcpcfg_diff}" ]]; then
      echo "ERROR: There are uncommitted changes in config files:" >&2
      echo "${ngcpcfg_diff}" >&2
      echo "ERROR: Please commit them or discard" >&2
      exit 1
    fi
  fi
}

install_repo() {
  local _repo="$1"

  if "${carrier_edition}"; then
    local _line1="deb [arch=amd64] ${ppa_url:-http://web01:9998}/autobuild ${_repo} main"
    local _line2="#deb-src ${ppa_url:-http://web01:9998}/autobuild ${_repo} main"
  fi
  if "${pro_edition}"; then
    local _line1="deb [arch=amd64] ${ppa_url:-http://sp:9998}/autobuild ${_repo} main"
    local _line2="#deb-src ${ppa_url:-http://sp:9998}/autobuild ${_repo} main"
  fi
  if "${ce_edition}"; then
    local _line1="deb [arch=amd64] ${ppa_url:-https://deb.sipwise.com}/autobuild ${_repo} main"
    local _line2="#deb-src ${ppa_url:-https://deb.sipwise.com}/autobuild ${_repo} main"
  fi
  if "${devel_mode}"; then
    echo "Devel mode requested, using direct connection to deb.sipwise.com (omit approx)"
    local _line1="deb [arch=amd64] ${ppa_url:-https://deb.sipwise.com}/autobuild ${_repo} main"
    local _line2="#deb-src ${ppa_url:-https://deb.sipwise.com}/autobuild ${_repo} main"
  fi

  if "${ngcpcfg}" ; then
    local ppa_num
    ppa_num=$(ngcpcfg get bootenv.ppa.size)
    if [[ "${ppa_num}" -ne 0 ]]; then
      ppa_num=$((ppa_num - 1))
      local count=0
      local ppa_name
      while [[ "${count}" -le "${ppa_num}" ]]; do
        ppa_name="$(ngcpcfg get bootenv.ppa.${count}.name)"
        if [[ "${ppa_name}" == "${_repo}" ]]; then
          return 0
        fi
        count=$(( count + 1 ))
      done
    fi

    local ppa_line='{'
    ppa_line+="'name','${_repo}'"
    if [[ -n "${ppa_url}" ]]; then
      ppa_line+=",'url','${ppa_url}'"
    fi
    if [[ "${apt_ppa_pin}" -ne 1001 ]]; then
       ppa_line+=",'priority','${apt_ppa_pin}'"
    fi
    ppa_line+='}'
    ngcpcfg set /etc/ngcp-config/config.yml bootenv.ppa.APPEND="${ppa_line}"
    return 0
  fi

  if grep -qs "${_line1}" "${c_dest}"; then
    echo "Repo ${_repo} already included into ${c_dest}. Nothing to do, skipping."
    return
  fi

  echo -n "Adding repo ${_repo} to ${c_dest} : "
  {
    echo "${_line1}"
    echo "${_line2}"
  } | ${sudo} tee -a "${c_dest}"

  echo "Feel free to run: apt-get update"
}

run_apt_update_upgrade() {
  declare -a _repos=("$@")

  if "${ngcpcfg}" ; then
    ngcpcfg build "${c_dest}" "${c_pref}"
    ngcpcfg --no-db-sync commit "Add new PPA(s) ${_repos[*]}"
  fi

  declare -a choices=()
  if "${devel_mode}"; then
    choices+=("aptitude update + upgrade")
    choices+=("aptitude update")
    choices+=("approx PPA + aptitude update + upgrade")
    choices+=("approx ALL + aptitude update + upgrade")
  else
    choices+=("approx PPA + aptitude update + upgrade")
    choices+=("approx ALL + aptitude update + upgrade")
    choices+=("aptitude update + upgrade")
    choices+=("aptitude update")
  fi
  choices+=("ngcp-update")
  choices+=("Exit")

  if ! "${force}"; then
    echo -e "\nShould I run something else for you?"
    select choice in "${choices[@]}"  ; do
      if [[ "${choice}" = "aptitude update" ]]; then
        update=true
        break
      elif [[ "${choice}" = "aptitude update + upgrade" ]]; then
        update=true
        upgrade=true
        break
      elif [[ "${choice}" = "approx PPA + aptitude update + upgrade" ]]; then
        approx_ppa=true
        update=true
        upgrade=true
        break
      elif [[ "${choice}" = "approx ALL + aptitude update + upgrade" ]]; then
        approx_all=true
        update=true
        upgrade=true
        break
      elif [[ "${choice}" = "ngcp-update" ]]; then
        ngcp_update=true
        break
      else
        echo "Exit as requested."
        exit 0
      fi
    done
  fi

  if "${carrier_edition}" || "${pro_edition}"; then
    if "${approx_ppa}"; then
      echo "Updating the APPROX meta data for PPA repo ONLY and make it available for the other nodes."
      echo "Running: ${sudo} ngcp-approx-cache --force --update-filter ${_repos[*]}"
      ngcp-approx-cache --force --update-filter "${_repos[*]}"
    elif "${approx_all}"; then
      echo "Updating the APPROX meta data for ALL repos and make it available for the other nodes."
      echo "Running: ${sudo} ngcp-approx-cache --force --auto"
      ngcp-approx-cache --force --auto
    fi
  fi

  if "${update}"; then
    echo "Running: ${sudo} aptitude update"
    ${sudo} NGCP_SHOW_HINT=false aptitude update
  fi

  if "${upgrade}"; then
    echo "Running: ${sudo} aptitude upgrade"
    ${sudo} DEBIAN_FRONTEND='noninteractive' \
      aptitude \
      --assume-yes \
      --safe-resolver \
      -o DPkg::Options::=--force-confask \
      -o DPkg::Options::=--force-confnew \
      upgrade
  fi

  if "${ngcp_update}"; then
    echo "Committing APT changes for PPA files to etckeeper"
    ${sudo} etckeeper commit "ngcp-ppa: commit PPA changes before ngcp-update" || true
    echo "Running: ${sudo} ngcp-update"
    ${sudo} FORCE=yes ngcp-update
  fi
}

install_preferences() {
  local _ppa="$1"
  local _pref="${c_pref}_${_ppa//\./_}"

  if "${ngcpcfg}" ; then
    echo "ngcpcfg is used to manage PPAs no need to manage preferences manually"
    return 0
  fi

  echo "Pinning PPA '${_ppa}' into ${_pref}"
  echo "Package: *" | ${sudo} tee "${_pref}" >/dev/null
  echo "Pin: release ${_ppa}" | ${sudo} tee -a "${_pref}" >/dev/null
  echo "Pin-Priority: ${apt_ppa_pin}" | ${sudo} tee -a "${_pref}" >/dev/null
}

get_repo_indexes() {
  declare -a _arr=("$@")
  local _repo_indexes=""
  local _state=true

  while "${_state}"; do
    read -r -p "Please, specify necessary repositories using space [3 5 7]: " _repo_indexes
    for i in ${_repo_indexes}; do
      _state=false
      if ! [[ $i =~ ^-?[0-9]+$  &&  $i -gt 0 &&  -n "${_arr[$((i-1))]}" ]]; then
        echo "Error: entered value '$i' is wrong, as such repo doesn't exist" >&2
        _state=true
      fi
    done
  done

  echo "${_repo_indexes}"
}

get_list() {
  declare -a _list=("$@")

  if [[ -z "${_list[*]}" && "${clean}" ]]; then
    echo "Nothing to do."
    exit 0
  fi

  if "${force}"; then
    install_preferences "${_list[*]}"
    for repo in "${_list[@]}"; do
        install_repo "${repo}"
    done
    run_apt_update_upgrade "${_list[@]}"
    return
  fi

  local _menu="${_list[*]} Multiple Exit"
  echo "Choose repository to add into ${c_dest} :"

  select repo in ${_menu} ; do

    if [[ -z "${repo}" || "${repo}" = "Exit" ]]; then
      echo "Exit as requested."
      exit 0

    elif [[ "${repo}" = "Multiple" ]]; then
      declare -a _approx_ppa=()
      local _repo_indexes
      _repo_indexes=$(get_repo_indexes "${_list[@]}")
      local _arr=("${_list[@]}")
      for i in ${_repo_indexes}; do
        install_repo "${_arr[$((i-1))]}"
        install_preferences "${_arr[$((i-1))]}"
        _approx_ppa+=("${_arr[$((i-1))]}")
      done
      run_apt_update_upgrade "${_approx_ppa[@]}"
      return

    else
      install_preferences "${repo}"
      install_repo "${repo}"
      run_apt_update_upgrade "${repo}"
      return
    fi
  done
}

clean_ppa() {
  if "${clean}"; then
    if "${ngcpcfg}" ; then
      ngcpcfg set /etc/ngcp-config/config.yml 'bootenv.ppa=[]'
      ngcpcfg build "${c_dest}" "${c_pref}"
      ngcpcfg commit "Clean all PPA(s)"
      return 0
    fi
    for _file in "${c_dest}" "${c_pref}"* ; do
      if [[ -f "${_file}" ]]; then
        echo "Cleanup requested, deleting ${_file}"
        ${sudo} rm -f "${_file}"
      fi
    done
  fi
}

get_ngcp_version() {
  if [[ ! -r /etc/ngcp_version ]]; then
    echo "Warning: unknown NGCP version, cannot continue." >&2
    return
  fi

  ngcp_version="$(cat /etc/ngcp_version)"
  echo "Current NGCP version: ${ngcp_version}"
  echo
}

get_ngcp_edition() {
  if ! "${ngcpcfg}"; then
    echo "ngcpcfg is disabled, no need to get ngcp edition"
    return 0
  fi

  if [[ ! -f /etc/default/ngcp-roles ]]; then
    echo "Error, missing file /etc/default/ngcp-roles" >&2
    exit 1
  fi

  # shellcheck disable=SC1091
  . /etc/default/ngcp-roles

  if [[ "${NGCP_TYPE}" = "carrier" ]]; then
    carrier_edition=true
  elif [[ "${NGCP_TYPE}" = "sppro" ]]; then
    pro_edition=true
  elif [[ "${NGCP_TYPE}" = "spce" ]]; then
    ce_edition=true
  else
    echo "Error: unknown NGCP_TYPE=${NGCP_TYPE}, cannot continue." >&2
    exit 1
  fi
}

### End functions block

configure "${list[@]}"
get_ngcp_edition
get_ngcp_version
check_env "${list[@]}"
clean_ppa
get_list "${list[@]}"
echo "Done"

exit 0
