#!/bin/bash
# Purpose: clean ngcpcfg config/templates
################################################################################

set -e
set -u

# support testsuite
FUNCTIONS="${FUNCTIONS:-/usr/share/ngcp-ngcpcfg/functions/}"
HELPER="${HELPER:-/usr/share/ngcp-ngcpcfg/helper/}"
SCRIPTS="${SCRIPTS:-/usr/share/ngcp-ngcpcfg/scripts/}"

if ! [ -r "${FUNCTIONS}"/main ] ; then
  printf "Error: %s/main could not be read. Exiting.\n" "${FUNCTIONS}" >&2
  exit 1
fi

# shellcheck source=./functions/main
. "${FUNCTIONS}"/main

cd "${NGCPCTL_MAIN}"

## functions {{{
clean_help() {
  export TIME_FORMAT=''
  log_info "Sample:"
  log_info "  ngcpcfg clean [--all] [--branches] [--force] [--help] [--reset-master] [--stashes] [--tracked-files] [--untracked-files]"
  log_info ""
  log_info "Run 'man ngcpcfg' for more information."
}

request_confirmation() {
  if "${force:-false}" ; then
    log_info "Forcing action due to option '--force'"
    return 0
  fi

  if ! tty -s; then
    log_warn "Cannot request confirmation (no tty). Hint: use option '--force'."
    return 1
  fi

  log_info_n "Please confirm 'yes' or 'no': "

  # clearing STDIN
  while read -r -t 0; do read -r; done

  while true ; do
    read -r a
    case "${a,,}" in
      yes) return 0 ;;
      no) return 1 ;;
      *) log_info_n "Please answer 'yes' or 'no': " ;;
    esac
    unset a
  done
}

clean_ensure_branch_master () {
  log_info "Ensure local branch is 'master':"

  local current_branch
  log_debug "Call: compare_active_branch 'master'"
  current_branch=$(compare_active_branch 'master')

  if [ "${current_branch}" = 'master' ] ; then
    log_info "OK: branch master active"
  else
    log_debug "git checkout master"
    if git checkout master ; then
      log_info "OK: checked out branch master "
    else
      log_error "Cannot checkout branch 'master'"
      return 1
    fi
  fi

  return 0
}

clean_tracked() {
  log_info "Removing all local changes (if any):"

  if is_git_clean ; then
    log_info "OK: no local changes found, nothing to clean here."
    return 0
  fi

  log_info "Found local changes in '${NGCPCTL_MAIN}':"
  log_debug "git status -s -uall"
  git status -s -uall

  log_info "Should we remove changes above?"
  if ! request_confirmation ; then
    log_info "Skipping cleanup of local changes as requested."
    return 0
  fi

  log_debug "git reset --hard HEAD"
  if git reset --hard HEAD ; then
    log_info "OK: Done"
  else
    log_error "Cannot reset local changes"
    return 1
  fi

  return 0
}

clean_untracked() {
  log_info "Removing all untracked files (if any):"

  local tmp
  tmp=$(mktemp -t ngcpcfg-clean-untracked.XXXXXXXXXX)

  log_debug "git clean -n -f -d -x -e sites/current"
  if ! git clean -n -f -d -x -e sites/current > "${tmp}" ; then
    log_error "Failed to collect list of untracked files"
    return 1
  fi

  if [ "$(wc -l < "${tmp}")" = "0" ] ; then
    log_info "OK: No untracked files found, nothing to clean here."
    return 0
  fi

  log_info "Found untracked files in '${NGCPCTL_MAIN}':"
  cat "${tmp}"

  log_info "Should we remove untracked files above?"
  if ! request_confirmation ; then
    log_info "Skipping cleanup of untracked files as requested."
    return 0
  fi

  log_debug "git clean -f -d -x -e sites/current"
  if ! git clean -f -d -x -e sites/current ; then
    log_error "Failed to clean list of untracked files"
    return 1
  fi

  if [ -n "${DEBUG:-}" ] ; then
    log_debug "Not removing temporary file ${tmp}"
  else
    rm -f "${tmp}"
  fi

  return 0
}

clean_stash() {
  log_info "Removing all stashes (if any):"

  local tmp
  tmp=$(mktemp -t ngcpcfg-clean-stash.XXXXXXXXXX)

  log_debug "git stash list"
  if ! git stash list > "${tmp}" ; then
    log_error "Failed to collect list of stashes"
    return 1
  fi

  if [ "$(wc -l < "${tmp}")" = "0" ] ; then
    log_info "OK: No stashes found, nothing to clean here."
    return 0
  fi

  log_info "Found stashes in '${NGCPCTL_MAIN}':"
  cat "${tmp}"

  log_info "Should we remove the stashes above?"
  if ! request_confirmation ; then
    log_info "Skipping stash cleanup as requested."
    return 0
  fi

  log_debug "git stash clear"
  if ! git stash clear ; then
    log_error "Failed to clean git stashes"
    return 1
  fi

  if [ -n "${DEBUG:-}" ] ; then
    log_debug "Not removing temporary file ${tmp}"
  else
    rm -f "${tmp}"
  fi

  return 0
}

clean_old_local_branches() {
  log_info "Removing all old branches (if any):"

  local tmp
  tmp=$(mktemp -t ngcpcfg-clean-oldbranch.XXXXXXXXXX)

  log_debug "git branch"
  if ! git branch > "${tmp}" ; then
    log_error "Failed to collect list of available branches"
    return 1
  fi

  # do not propose to delete active branch and branch 'master'
  sed -i -r '/^\* /d' "${tmp}"
  sed -i -r '/ master$/d' "${tmp}"

  if [ "$(wc -l < "${tmp}")" = "0" ] ; then
    log_info "OK: No branches to delete found, nothing to clean here."
    return 0
  fi

  log_info "Found branches to be removed:"
  cat "${tmp}"

  log_info "Should we remove the branches above?"
  if ! request_confirmation ; then
    log_info "Skipping branches cleanup as requested."
    return 0
  fi

  while read -r branch ; do
    log_debug "git branch -D '${branch}'"
    if ! git branch -D "${branch}" ; then
      log_error "Failed to delete local branch '${branch}'"
      return 1
    fi
  done < "${tmp}"

  if [ -n "${DEBUG:-}" ] ; then
    log_debug "Not removing temporary file ${tmp}"
  else
    rm -f "${tmp}"
  fi

  return 0
}

clean_reset_master() {
  log_debug "checking 'origin' availability using: git remote show origin"
  if ! git remote show origin >/dev/null 2>&1 ; then
    log_error "Missing git origin 'origin'. Aborting"
    return 1
  fi

  local tmp
  tmp=$(mktemp -t ngcpcfg-clean-reset.XXXXXXXXXX)

  log_debug "git diff origin/master..master"
  if ! git diff origin/master..master > "${tmp}" ; then
    log_error "Failed to collect diff of 'origin/master' and 'master' branches"
    return 1
  fi

  if [ "$(wc -l < "${tmp}")" = "0" ] ; then
    log_info "OK: No committed and push pending changes found, nothing to clean here."
    return 0
  fi

  log_info "Found committed local changes to be removed:"
  cat "${tmp}"

  log_info "Should we restore branch 'master' from origin to remove local commit?"
  if ! request_confirmation ; then
    log_info "Skipping restore branch 'master' from origin as requested."
    return 0
  fi

  log_info "Restoring branch 'master' from origin:"

  local new_branch
  new_branch="ngcpcfg_backup_$(date +%s)"

  log_debug "git branch -m '${new_branch}'"
  if ! git branch -m "${new_branch}" ; then
    log_error "Cannot rename current branch to '${new_branch}'. Aborting"
    return 1
  fi
  log_info "OK: renamed current branch to '${new_branch}'. Feel free to clean it."

  log_debug "git checkout -b master --track origin/master"
  if ! git checkout -b master --track origin/master ; then
    log_error "Cannot checkout branch 'master'"
    return 1
  fi

  log_info "OK: created local branch 'master' to track 'origin/master'"
  return 0
}

## }}}

RC=0
branches=false
force=false
help=false
reset_master=false
stashes=false
tracked_files=false
untracked_files=false

clean_options="all,branches,force,help,reset-master,stashes,tracked-files,untracked-files"
if ! _opt_temp=$(getopt --name "$0" -o +h --long ${clean_options} -- "$@") ; then
  log_error "Try 'ngcpcfg clean --help' for more information."
  exit 1
fi
eval set -- "${_opt_temp}"

[ "$1" = "--" ] && help=true

while : ; do
  case "$1" in
    --all)
      branches=true
      reset_master=true
      stashes=true
      tracked_files=true
      untracked_files=true
      ;;
    --branches)        branches=true        ;;
    --force)           force=true           ;;
    --help)            help=true            ;;
    --reset-master)    reset_master=true    ;;
    --stashes)         stashes=true         ;;
    --tracked-files)   tracked_files=true   ;;
    --untracked-files) untracked_files=true ;;
    --) shift ; break ;;
    *) log_error "Unknown option '$1'" ; exit 1 ;;
  esac
  shift
done

if ${help} ; then
  clean_help
  exit 0
fi

if [ ! -f "${FUNCTIONS}/ha_features" ] ; then
  log_debug "Skip function 'clean_reset_master'. It is for HA installation only."
  reset_master=false
fi

if ! clean_ensure_branch_master ; then
  exit 1
fi

if ${tracked_files} ; then
  clean_tracked || RC=$((RC + $?))
fi

if ${untracked_files} ; then
  clean_untracked || RC=$((RC + $?))
fi

if ${stashes} ; then
  clean_stash || RC=$((RC + $?))
fi

if ${reset_master} ; then
  clean_reset_master || RC=$((RC + $?))
fi

if ${branches} ; then
  clean_old_local_branches || RC=$((RC + $?))
fi

if [ "${RC}" = "0" ] ; then
  log_info "All operations were finished successfully. Good to be clean!"
else
  log_error "Some operations finished with an error, see details above."
fi

exit "${RC}"

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