#!/bin/bash

UPDATEDIR=/usr/share/ngcp-cfg-schema/cfg_scripts
CONFIGDIR=/etc/ngcp-config
CONFIGDIR_SET=false
ROLESFILE=/etc/default/ngcp-roles
OPT_NGCP_TYPE=''

# iff executed under VERBOSE=yes provide more information
verbose() {
  if [[ "$VERBOSE" == "yes" ]] ; then
    echo "$*"
  fi
}

usage() {
  echo -e "Usage: $0 [OPTION]..." 1>&2
  echo -e "\t-f: Force update, even if running on active node" 1>&2
  echo -e "\t-i: init dbfile and exit afterwards" 1>&2
  echo -e "\t-t <node>: run in test suite mode, needs an explicit -d" 1>&2
  echo -e "\t-d <dir>: the output directory (default '$CONFIGDIR')" 1>&2
  echo -e "\t-n <ngcp type>: the NGCP_TYPE (defaults to the one set in ${ROLESFILE})" 1>&2
  echo -e "\t-u <dir>: the update scripts directory (default '${UPDATEDIR}')" 1>&2
  echo >&2
  echo "To use the test mode, you need to explicitly specify an output" >&2
  echo "directory, to avoid global breakage, even if using the default." >&2
  echo "By default it will set the update dir to cfg_scripts/, so you" >&2
  echo "might also need to use -u." >&2
}

TESTSUITE_MODE=false
FORCE_ACTIVE_NODE=false

while getopts "fhit:d:u:n:" opt; do
    case "${opt}" in
        i)
            INIT_DBFILE=true
            ;;
        f)
            FORCE_ACTIVE_NODE=true
            ;;
        t)
            export TESTSUITE_MODE=true
            TESTSUITE_NODENAME="$OPTARG"
            ;;
        d)
            CONFIGDIR="$OPTARG"
            CONFIGDIR_SET=true
            mkdir -p "$CONFIGDIR"
            ;;
        u)
            UPDATEDIR="${OPTARG}"
            ;;
        n)
            OPT_NGCP_TYPE="${OPTARG}"
            ;;
        h)
            usage; exit 0;
            ;;
        *)
            usage; exit 1;
            ;;
    esac
done
shift $((OPTIND-1))

DBFILE="$CONFIGDIR/cfg_schema.db"

nodename=$(/usr/sbin/ngcp-nodename 2>/dev/null || echo "unknown")

if ! [ -d "${UPDATEDIR}" ]; then
  echo "Error: update scripts directory '${UPDATEDIR}' does not exist." >&2
  exit 1
fi

if $TESTSUITE_MODE; then
  if ! ${CONFIGDIR_SET}; then
    echo "Error: test mode requires setting -d explicitly." >&2
    exit 1
  fi
  if [ -n "$TESTSUITE_NODENAME" ]; then
    nodename="$TESTSUITE_NODENAME"
  fi
fi
if [ "${nodename}" = "unknown" ] ; then
  echo "Error: nodename could not be identified." >&2
  exit 1
fi

if [[ -n "${OPT_NGCP_TYPE:-}" ]]; then
  echo "using ${OPT_NGCP_TYPE} as NGCP_TYPE"
  NGCP_TYPE="${OPT_NGCP_TYPE}"
elif [ -r "$ROLESFILE" ]; then
  # shellcheck disable=SC1090
  . "$ROLESFILE"
else
  echo "Error: cannot read $ROLESFILE" >&2
  exit 1
fi

# For some of the upgrade scripts we need an easy way to distinguish which
# node type we are running this on.
export NGCP_TYPE

init_dbfile() {
  INIT_FILE="$UPDATEDIR/init/init_cfg_schema.sql"
  if ! sqlite3 "$DBFILE" < "$INIT_FILE" ; then
    echo "Error: can't create $DBFILE" >&2
    exit 2
  fi
}

pending_revisions() {
  local revs="$1"

  if $TESTSUITE_MODE; then
    # When running under a testsuite assume everything is pending.
    echo "$revs"
  else
    # shellcheck disable=SC2086
    ngcp-check-rev-applied \
      --dbfile "$DBFILE" --schema cfg_schema \
      --revision $revs \
      | awk '/^No match for revision/ {print $5}'
  fi
}

apply_revision() {
  [ -n "$1" ] || return 1
  [ -n "$2" ] || return 2
  [ -n "$3" ] || return 3

  revs="$1"
  inputfile="$2"
  outputfile="$3"

  printf "Processing missing revision scripts for %s ...\n" "$outputfile"
  for missing_revision in $(pending_revisions "$revs") ;
  do
    revname=$(basename "$missing_revision")

    # Custom $CONFIGDIR/config.d/*.yml files handling
    # because we don't get static filenames as arguments
    if [ "$inputfile" = "config_d" ] ; then
        config_dir="$(dirname "$missing_revision")"
        config_file="$(basename "$config_dir")"
        mkdir -p "$CONFIGDIR/config.d"
        inputfile="$CONFIGDIR/config.d/${config_file}.yml"
        outputfile="$CONFIGDIR/config.d/${config_file}.yml"
    fi

    if [ "$NGCP_TYPE" != 'spce' ] && [[ "$revname" == *_ce.up ]] ; then
      verbose "Not considering $revname as running PRO/CARRIER system."
      continue
    fi

    if [ "$NGCP_TYPE" = 'spce' ] && [[ "$revname" == *_pro.up ]] ; then
      verbose "Not considering $revname as not running a PRO/CARRIER system."
      continue
    fi

    printf "Applying revision script %s: " "$revname"
    if "$missing_revision" "$inputfile" "$outputfile" ; then
      echo "done"
    else
      echo "failed. :(" >&2
      echo "Please resolve the problem and run ngcp-update-cfg-schema again." >&2
      exit 1
    fi

    if [[ "$revname" =~ ^[0]*([0-9]+)_ ]] ; then
      short_rev=${BASH_REMATCH[1]}
    fi
    SQL_COMMAND="insert into cfg_schema(revision, node) values ('${short_rev}', '${nodename}');"
    if sqlite3 "$DBFILE" "$SQL_COMMAND"; then
      verbose "Marked revision $revname as applied."
    else
      echo "Error while executing DB commands using revision $revname for host $nodename" >&2
      exit 1
    fi

  done
}

replace_password_placeholders() {
  local file="$1"
  local num_of_characters="$2"

  if ! $TESTSUITE_MODE; then
    "${UPDATEDIR}/../helper/ngcp-replace-placeholder-passwords" \
      "${file}" "${num_of_characters}"
  fi
}

revision_wrapper() {
  local revlist_constants revlist_config revlist_maintenance revlist_network revlist_config_d

  [ -n "$1" ] || return 1
  # shellcheck disable=SC2048
  for rev in $* ; do
    if ! [ -x "$rev" ] ; then
      echo "Error: $rev can not be executed." >&2
      exit 1
    fi

    cfgtype=$(dirname "$rev")

    # generate list of revision scripts that should be executed
    case "$cfgtype" in
      */constants)
        revlist_constants="$revlist_constants $rev"
        ;;
      */config)
        revlist_config="$revlist_config $rev"
        ;;
      */maintenance)
        revlist_maintenance="$revlist_maintenance $rev"
        ;;
      */network)
        revlist_network="$revlist_network $rev"
        ;;
      */config.d/*)
        revlist_config_d="$revlist_config_d $rev"
        ;;
    esac

  done

  apply_revision "$revlist_constants"   "$CONFIGDIR/constants.yml"   "$CONFIGDIR/constants.yml"
  apply_revision "$revlist_config"      "$CONFIGDIR/config.yml"      "$CONFIGDIR/config.yml"
  apply_revision "$revlist_maintenance" "$CONFIGDIR/maintenance.yml" "$CONFIGDIR/maintenance.yml"
  apply_revision "$revlist_network"     "$CONFIGDIR/network.yml"     "$CONFIGDIR/network.yml"
  apply_revision "$revlist_config_d"    config_d config_d
}

running_on_active_node() {
  if [ "$NGCP_TYPE" = "spce" ] ; then
    return 1
  else
    if $TESTSUITE_MODE; then
      return 1
    fi
    if /usr/sbin/ngcp-check-active -q ; then
      if $FORCE_ACTIVE_NODE; then
        echo "Force update on active node as requested."
        return 1
      else
        echo "This seems to be the active node, nothing to do."
        return 0
      fi
    else
      echo "This seems to be the inactive node, applying revisions."
      return 1
    fi
  fi
}

if ! running_on_active_node ; then
  if ! [ -r "$DBFILE" ] ; then
    if $TESTSUITE_MODE; then
      init_dbfile
    elif $INIT_DBFILE ; then
      init_dbfile
      echo "$DBFILE created, exiting now"
      exit 0
    else
      echo "Error: $DBFILE not found" >&2
      exit 1
    fi
  fi

  if $TESTSUITE_MODE && [ -d "$UPDATEDIR/init/" ]; then
    apply_revision "$(ls -1 -v "$UPDATEDIR"/init/*config*.up)" "$CONFIGDIR/config.yml" "$CONFIGDIR/config.yml"
    apply_revision "$(ls -1 -v "$UPDATEDIR"/init/*constants*.up)" "$CONFIGDIR/constants.yml" "$CONFIGDIR/constants.yml"
    apply_revision "$(ls -1 -v "$UPDATEDIR"/init/*maintenance*.up)" "$CONFIGDIR/maintenance.yml" "$CONFIGDIR/maintenance.yml"
  fi

  # make sure we get sorted 10XXX after 9XXX
  if [ -d "$UPDATEDIR/config/" ]; then
    revision_wrapper "$(ls -1 -v "$UPDATEDIR"/config/*.up)"
    replace_password_placeholders "$CONFIGDIR/config.yml" "16"
  fi

  if [ -d "$UPDATEDIR/constants/" ]; then
    revision_wrapper "$(ls -1 -v "$UPDATEDIR"/constants/*.up)"
    replace_password_placeholders "$CONFIGDIR/constants.yml" "20"
  fi

  if [ -d "$UPDATEDIR/maintenance/" ]; then
    revision_wrapper "$(ls -1 -v "$UPDATEDIR"/maintenance/*.up)"
  fi

  if [ -d "$UPDATEDIR/network/" ]; then
    revision_wrapper "$(ls -1 -v "$UPDATEDIR"/network/*.up)"
  fi

  if [ -d "$UPDATEDIR/config.d/" ]; then
    # shellcheck disable=SC2044
    for dir in $(find "$UPDATEDIR"/config.d/ -maxdepth 1  -mindepth 1 -type d) ; do
      revision_wrapper "$(ls -1 -v "$dir"/*.up)"
    done
  fi
fi

## END OF FILE #################################################################
