#!/usr/bin/env sh

THIS_FILE="$(readlink -f "$0" || realpath "$0")"
THIS_DIR="$(dirname "${THIS_FILE}")"
THIS_BASENAME="$(basename "${THIS_FILE}")"
COMPOSE_DIR_PREV="${COMPOSE_DIR}"
COMPOSE_DIR="$(dirname "${THIS_DIR}")/birdhouse"

export BIRDHOUSE_COMPOSE="${BIRDHOUSE_COMPOSE:-"${COMPOSE_DIR}/birdhouse-compose.sh"}"
export __BIRDHOUSE_SUPPORTED_INTERFACE=True

USAGE="USAGE: $THIS_BASENAME [-h|--help]
                             [-b|--backwards-compatible]
                             [-e|--env-file path]
                             [-q|--quiet [level]]
                             [-s|--log-stdout [level]]
                             [-l|--log-file [level] path]
                             [-L|--log-level level]
                             {info|compose|configs|backup}"
USAGE=$(echo $USAGE | tr "\n" " ")

HELP="$USAGE

Manage the Birdhouse software stack.

Commands:
  info        Print build information
  compose     Run a \"docker compose\" command for the Birdhouse project (run '${THIS_BASENAME} compose --help' for more options)
  configs     Load or execute commands in the Birdhouse configuration environment
  backup      Backup or recover data from a running birdhouse environment

Options:
  -h, --help                                              Print this message and exit
  -b, --backwards-compatible                              Run in backwards compatible mode
  -e, --env-file path                                     Override the local environment file, default is ${COMPOSE_DIR}/env.local
  -s, --log-stdout                                        Write logs to stdout for all log levels, default is to write to stderr
  -s, --log-stdout {DEBUG|INFO|WARN|ERROR|CRITICAL}       Write logs to stdout for the given log level only (this option can be repeated)
  -l, --log-file path                                     Write logs to this file path for all log levels
  -l, --log-file {DEBUG|INFO|WARN|ERROR|CRITICAL} path    Write logs to this file path for the given log level only (this option can be repeated), this takes precedence over the --log-file option for all log levels
  -q, --quiet                                             Do not write logs to stdout or stderr for all log levels. Logs will still be written to a file if --log-file is set
  -q, --quiet {DEBUG|INFO|WARN|ERROR|CRITICAL}            Do not write logs to stdout or stderr for the given log level only (this option can be repeated), Logs will still be written to a file if --log-file is set
  -L, --log-level {DEBUG|INFO|WARN|ERROR}                 Set log level, default is INFO
"

CONFIGS_USAGE="USAGE: $THIS_BASENAME [${THIS_BASENAME} options] configs [-h|--help] [-b|--basic] {[-p|--print-config-command] | [-c|--command command] | [--print-log-command]}"
CONFIGS_HELP="$CONFIGS_USAGE

Load or execute commands in the Birdhouse configuration environment.

Options:
  -h, --help                   Print this message and exit
  -b, --basic                  Only load/print a command for the basic configuration settings, not those specified by additional components
  -p, --print-config-command   Print a command that can be used to load configuration settings as environment variables
  -c, --command string         Execute the given command after loading configuration settings
  --print-log-command          Print a command that can be used to load the 'log' function used by birdhouse
Deprecated Options:
  -q, --quiet                  Suppress stdout when loading configuration settings for the '--command' option. [DEPRECATED: use the --quiet option directly under birdhouse instead]
  -d, --default                Same as the --basic flag. [DEPRECATED: use the --basic flag instead]

Example Usage:

  $ ${THIS_BASENAME} configs -c 'echo \${BIRDHOUSE_FQDN}'
  example.com  # This is the value of BIRDHOUSE_FQDN as determined by the current configuration settings
  $ ${THIS_BASENAME} configs -p
  . /path/to/configs/file/to/source && read_configs
  $ eval \$(${THIS_BASENAME} configs -p)
  $ echo \${BIRDHOUSE_FQDN}
  example.com  # This is the value of BIRDHOUSE_FQDN as determined by the current configuration settings
  $ eval \$(${THIS_BASENAME} configs --print-log-command)
  log INFO 'Now I can write log messages!'
"

BACKUP_USAGE="USAGE: $THIS_BASENAME [${THIS_BASENAME} options] backup [-h|--help] {create,restore,restic}"

BACKUP_CREATE_USAGE="USAGE: $THIS_BASENAME [${THIS_BASENAME} options] backup [backup options] create [-h|--help]
                                                                                                     [-d|--dry-run]
                                                                                                     [[-a|--component-application-data COMPONENT], ...]
                                                                                                     [[-u|--component-user-data COMPONENT], ...]
                                                                                                     [[-l|--component-logs-data COMPONENT], ...]
                                                                                                     [--birdhouse-logs]
                                                                                                     [--local-env-file]
                                                                                                     [--no-restic]"
BACKUP_CREATE_USAGE=$(echo $BACKUP_CREATE_USAGE | tr "\n" " ")

BACKUP_RESTORE_USAGE="USAGE: $THIS_BASENAME [${THIS_BASENAME} options] backup [backup options] restore (-s|--snapshot SNAPSHOT | --no-restic)
                                                                                                       [-h|--help]
                                                                                                       [-d|--dry-run]
                                                                                                       [[-a|--component-application-data COMPONENT], ...]
                                                                                                       [[-u|--component-user-data COMPONENT], ...]
                                                                                                       [[-l|--component-logs-data COMPONENT], ...]
                                                                                                       [--birdhouse-logs]
                                                                                                       [--local-env-file]
                                                                                                       [--no-clobber]"
BACKUP_RESTORE_USAGE=$(echo $BACKUP_RESTORE_USAGE | tr "\n" " ")

BACKUP_HELP="$BACKUP_USAGE

Manage backups for the birdhouse stack.

Commands:
  create:     Create a backup for data used by the birdhouse stack.
  restore:    Restore data from a backup snapshot.
  restic:     Run a command using the restic CLI (birdhouse uses restic to manage backups).

Options:
  -h, --help    Print this message and exit
"

BACKUP_CREATE_HELP="$BACKUP_CREATE_USAGE

Create a backup for data used by the birdhouse stack.

Options:
  -h, --help                          Print this message and exit
  -d, --dry-run                       Print information about which data will be backed up. Do not actually perform backup.
  -a, --component-application-data    Backup application data for a given component (e.g. magpie). This can be specified multiple times. Use '*' to select all components.
  -u, --component-user-data           Backup user data for a given component (e.g. cowbird). This can be specified multiple times. Use '*' to select all components.
  -l, --component-logs-data           Backup logs data for a given component (e.g. thredds). This can be specified multiple times. Use '*' to select all components.
  -r, --representative-data           Backup representative data for a given component (e.g. stac). This can be specified multiple times. Use '*' to select all components.
  --birdhouse-logs                    Backup birdhouse logs and docker container logs
  --local-env-file                    Backup local environment file
  --no-restic                         Do not save this backup as a restic snapshot. This will only backup the data to the volume specified by the BIRDHOUSE_BACKUP_VOLUME variable.

Note:
  Not all components store application data, user data, or log data. Depending on the component, some or none of these
  will be available to backup. Use the --dry-run flag to confirm which data will be backed up.

Note:
  The volume specified by the BIRDHOUSE_BACKUP_VOLUME variable will be emptied before every backup operation (unless the --dry-run flag or --no-restic is specified).
  If you want to preserve the content of this directory, copy it somewhere else first, or employ a directory path combined with --no-restic option.

Example Usage:

  $ birdhouse backup create -a '*' -u '*' -l '*' --birdhouse-logs --local-env-file    # backs up everything
  $ birdhouse backup create -a magpie                                                 # backs up application data for the magpie component
  $ birdhouse backup create -a cowbird -u cowbird -a finch                            # backs up application data for cowbird and finch and the user data for cowbird
"

BACKUP_RESTORE_HELP="$BACKUP_RESTORE_USAGE

Restore data from a backup snapshot.

WARNING: Running this command without the --no-clobber option will replace the current birdhouse data with the data
         recovered from the backup. This will remove all current birdhouse data first and is not reversible!
         Please consider doing a backup of the current data before restoring historical data from a snapshot.

Options:
  -h, --help                          Print this message and exit
  -s, --snapshot                      ID of the snapshot that should be recovered. To list available snapshots run: ${THIS_BASENAME} backup restic snapshots
  -d, --dry-run                       Print information about which data will be restored. Do not actually perform restore.
  -a, --component-application-data    Restore application data for a given component (e.g. magpie). This can be specified multiple times. Use '*' to select all components.
  -u, --component-user-data           Restore user data for a given component (e.g. cowbird). This can be specified multiple times. Use '*' to select all components.
  -l, --component-logs-data           Restore logs data for a given component (e.g. thredds). This can be specified multiple times. Use '*' to select all components.
  -r, --representative-data           Restore representative data for a given component (e.g. stac). This can be specified multiple times. Use '*' to select all components.
  --birdhouse-logs                    Restore birdhouse logs and docker container logs
  --local-env-file                    Restore local environment file
  --no-restic                         Do not employ any restic snapshot as backup. Backup data will be recovered directly from the volume specified by the BIRDHOUSE_BACKUP_VOLUME variable.
  --no-clobber                        Do not overwrite current files with restored backup. This will only recover the data to the volume specified by the BIRDHOUSE_BACKUP_VOLUME variable.


Note:
  Not all components store application data, user data, or log data. Depending on the component, some or none of these
  will be available to restore. Use the --dry-run flag to confirm which data will be restored.

Note:
  Log data and the local environment file will never overwrite current files with the restored backup.
  The data will be restored to the the volume specified by the BIRDHOUSE_BACKUP_VOLUME variable.
  In other words, restoring logs and the local environment file will behave as if the '--no-clobber' flag was provided.

Note:
  The volume specified by the BIRDHOUSE_BACKUP_VOLUME variable will be emptied before every restore operation (unless the --dry-run flag or --no-restic is specified).
  If you want to preserve the content of this directory, copy it somewhere else first, or employ a directory path combined with --no-restic option.

Note:
  To identify the restic restore snapshot IDs, restic commands can be invoked as follows:

  $ birdhouse backup restic --help      # prints help options for the restic command
  $ birdhouse backup restic snapshots   # list available restic snapshots including their IDs

Example Usage:

  $ birdhouse backup restore --snapshot latest -a '*' -u '*' -l '*' --birdhouse-logs --local-env-file   # restores everything from the latest snapshot
  $ birdhouse backup restore --snapshot XX123ABC -a magpie                                              # restores application data for the magpie component from the snapshot with id XX123ABC
  $ birdhouse backup restore --snapshot latest -u jupyterhub --no-clobber                               # restore user data for jupyterhub but don't overwrite current jupyterhub user data
  $ birdhouse backup restore --no-restic -r stac                                                        # restore representative data for STAC directly from BIRDHOUSE_BACKUP_VOLUME directory
"


READ_CONFIGS_CMD=read_configs

# Print a command that can be used to load configuration settings as environment variables.
# Modifies the command based on whether the options --backwards-compatible and --env-file are set in order
# to respect these settings if this command is run `eval` later on.
# Tries to set the BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED and BIRDHOUSE_LOCAL_ENV environment variables back to their
# original setting after the command is run with `eval`.
# Known issue: The original setting isn't restored if this script is called with an inline environment variable:
#   $ export $BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED=False
#   $ BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED=True birdhouse configs -p
#   ...
#   $ echo $BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED
#   True
print_config_command() {
  option="$1"
  configs_cmd_prefix="export __BIRDHOUSE_SUPPORTED_INTERFACE=True ; export COMPOSE_DIR=${COMPOSE_DIR} ;"
  configs_cmd_suffix="unset __BIRDHOUSE_SUPPORTED_INTERFACE ; COMPOSE_DIR='${COMPOSE_DIR_PREV}' ; "
  if [ "${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED+set}" = 'set' ]; then
    configs_cmd_prefix="${configs_cmd_prefix} export BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED='${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED}' ;"
  fi
  if [ "${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED_PREV+set}" = 'set' ]; then
    configs_cmd_suffix="${configs_cmd_suffix} export BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED='${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED_PREV}' ;"
  elif [ "${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED_UNSET}" = 'True' ]; then
    configs_cmd_suffix="${configs_cmd_suffix} unset BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED ;"
  fi

  if [ "${BIRDHOUSE_LOCAL_ENV+set}" = 'set' ]; then
    configs_cmd_prefix="${configs_cmd_prefix} export BIRDHOUSE_LOCAL_ENV='${BIRDHOUSE_LOCAL_ENV}' ;"
  fi
  if [ "${BIRDHOUSE_LOCAL_ENV_PREV+set}" = 'set' ]; then
    configs_cmd_suffix="${configs_cmd_suffix} export BIRDHOUSE_LOCAL_ENV='${BIRDHOUSE_LOCAL_ENV_PREV}' ;"
  elif [ "${BIRDHOUSE_LOCAL_ENV_UNSET}" = 'True' ]; then
    configs_cmd_suffix="${configs_cmd_suffix} unset BIRDHOUSE_LOCAL_ENV ;"
  fi

  if [ "${option}" = "no-suffix" ]; then
    configs_cmd_suffix=
  fi
  echo "${configs_cmd_prefix} . ${COMPOSE_DIR}/read-configs.include.sh; ${READ_CONFIGS_CMD} ; ${configs_cmd_suffix}"
}

print_log_command() {
  echo ". ${COMPOSE_DIR}/scripts/logging.include.sh"
}

# Support multiple short flags together (ex: -abc instead of -a -b -c)
# The first argument is the parse function to call once the multiple short flags have been parsed
# The rest of the arguments are the rest arguments to be processed.
parse_multiple_short_flags() {
  parse_func=$1
  shift
  new_flags="${1%%=*}"
  arg_value="${1#*=}"
  [ "${arg_value}" = "$1" ] && unset arg_value
  new_flags="$(echo "${new_flags#-*}" | sed 's/[a-z]/ -&/g')"
  shift
  if [ "${arg_value+set}" = 'set' ]; then
    # shellcheck disable=SC2086
    ${parse_func} ${new_flags} "${arg_value}" "$@"
  else
    # shellcheck disable=SC2086
    ${parse_func} ${new_flags} "$@"
  fi
}

# Parse arguments and options for the configs subcommand
parse_configs_args() {
  case "$1" in
    -d|--default)
      source_log
      log WARN "DEPRECATED: the '${THIS_BASENAME} configs --default' flag is deprecated use the --basic flag instead"
      shift
      parse_configs_args --basic "$@"
      ;;
    -b|--basic)
      READ_CONFIGS_CMD=read_basic_configs_only
      shift
      parse_configs_args "$@"
      ;;
    -q|--quiet)
      CONFIGS_QUIET=True
      source_log
      log WARN "DEPRECATED: the '${THIS_BASENAME} configs --quiet' flag is deprecated use the '${THIS_BASENAME} --quiet' flag instead"
      shift
      parse_configs_args "$@"
      ;;
    -p|--print-config-command)
      # Cannot be called with the --command or --print-log-command argument as well
      [ "${CONFIGS_CMD+set}" = 'set' ] && parse_configs_args 'Choose either --print-config-command or --command'
      [ "${LOG_INCLUDE_PRINT}" = 'True' ] && parse_configs_args 'Choose either --print-config-command or --print-log-command'
      CONFIGS_PRINT=True
      shift
      parse_configs_args "$@"
      ;;
    --print-log-command)
      # Cannot be called with the --print-config-command or --command option as well
      [ "${CONFIGS_CMD+set}" = 'set' ] && parse_configs_args 'Choose either --print-log-command or --command'
      [ "${CONFIGS_PRINT}" = 'True' ] && parse_configs_args 'Choose either --print-log-command or --print-config-command'
      LOG_INCLUDE_PRINT=True
      shift
      parse_configs_args "$@"
      ;;
    -c=*|--command=*)
      arg_value="${1#*=}"
      shift
      parse_configs_args -c "${arg_value}" "$@"
      ;;
    -c|--command)
      # Cannot be called with the --print-config-command or --print-log-command option as well
      [ "${CONFIGS_PRINT}" = 'True' ] && parse_configs_args 'Choose either --command or --print-config-command'
      [ "${LOG_INCLUDE_PRINT}" = 'True' ] && parse_configs_args 'Choose either --command or --print-log-command'
      shift
      CONFIGS_CMD="$1"
      shift
      parse_configs_args "$@"
      ;;
    -h|--help)
      echo "${CONFIGS_HELP}" | more
      ;;
    -??*)
      parse_multiple_short_flags parse_configs_args "$@"
      ;;
    '')
      # Print the configuration settings command or execute the specified
      # command once all other options have been parsed
      if [ "${CONFIGS_PRINT}" = 'True' ]; then
        print_config_command
      elif [ "${LOG_INCLUDE_PRINT}" = 'True' ]; then
        print_log_command
      elif [ "${CONFIGS_CMD+set}" = 'set' ]; then
        if [ "${CONFIGS_QUIET}" = "True" ]; then
          source_env 2> /dev/null
        else
          source_env
        fi
        exec /usr/bin/env sh -c "${CONFIGS_CMD}"
      else
        parse_configs_args 'Choose either --command, --print-config-command, or --print-log-command'
      fi
      ;;
    *)
      >&2 echo "$@"
      >&2 echo "$CONFIGS_USAGE"
      exit 1
      ;;
  esac
}

# Source the birdhouse environment. This will only work the first time it is
# called so that it can be called multiple times within the same process without
# triggering a re-read of the configuration settings every time.
source_env() {
  if [ "$_BIRDHOUSE_ENV_ALREADY_SOURCED" != "true" ]; then
    eval "$(print_config_command no-suffix)"
    _BIRDHOUSE_ENV_ALREADY_SOURCED=true
  fi
}

# Source the birdhouse logging environment. This will only work the first time it is
# called so that it can be called multiple times within the same process without
# triggering a re-read of the configuration settings every time.
source_log() {
  if [ "$_BIRDHOUSE_LOG_ENV_ALREADY_SOURCED" != "true" ]; then
    eval "$(print_log_command no-suffix)"
    _BIRDHOUSE_LOG_ENV_ALREADY_SOURCED=true
  fi
}

# Ensure that the birdhouse stack is running.
# If the stack is not running, this will raise a log ERROR message and exit.
ensure_birdhouse_running() {
  source_env
  if ! docker ps --filter label=com.docker.compose.project --format '{{.Label "com.docker.compose.project"}}' | grep -q "${COMPOSE_PROJECT_NAME:-birdhouse}"; then
      log ERROR "This command requires that the birdhouse stack be running. Start the stack first with '${THIS_BASENAME} compose up -d' and try again."
      exit 1
  fi
}

log_error_help() {
  log ERROR "$1"
  >&2 echo "$2"
  exit 1
}

# Perform pre-checks of arguments/configuration combinations relevant for backup creation.
check_backup_create_args() {
  if [ -z "${BIRDHOUSE_BACKUP_VOLUME}" ]; then
    log_error_help \
      "BIRDHOUSE_BACKUP_VOLUME must be specified." \
      "${BACKUP_CREATE_USAGE}"
  fi
  [ -n "${BIRDHOUSE_BACKUP_RESTORE_SNAPSHOT}" ] || \
  [ "${BIRDHOUSE_BACKUP_NO_RESTIC}" = 'true' ] || \
    log_error_help \
      'Use the -s|--snapshot option to specify a snapshot to restore with restic or --no-restic to employ BIRDHOUSE_BACKUP_VOLUME directly.' \
      "${BACKUP_CREATE_USAGE}"
  if
    [ -z "${BIRDHOUSE_BACKUP_RESTORE_SNAPSHOT}" ] && \
    [ "${BIRDHOUSE_BACKUP_NO_RESTIC}" = 'true' ] && \
    [ $(echo "${BIRDHOUSE_BACKUP_VOLUME}" | grep -c '/' || true) -eq 0 ]; then
      log WARN \
          "Backup create detected without an explicit directory path [BIRDHOUSE_BACKUP_VOLUME=${BIRDHOUSE_BACKUP_VOLUME}]." \
          "This will automatically create/use a docker named-volume."
  fi
}

# Perform pre-checks of arguments/configuration combinations relevant for backup restore.
check_backup_restore_args() {
  if [ -z "${BIRDHOUSE_BACKUP_VOLUME}" ]; then
    log_error_help \
      "BIRDHOUSE_BACKUP_VOLUME must be specified." \
      "${BACKUP_RESTORE_USAGE}"
  fi
  if [ "${BIRDHOUSE_BACKUP_NO_RESTIC}" = 'true' ]; then
    if [ $(echo "${BIRDHOUSE_BACKUP_VOLUME}" | grep -c '/' || true) -eq 0 ]; then
      log WARN \
          "Backup restore detected without an explicit directory path [BIRDHOUSE_BACKUP_VOLUME=${BIRDHOUSE_BACKUP_VOLUME}]." \
          "This will automatically create/use a docker named-volume."
    fi
  else
    if [ $(echo "${BIRDHOUSE_BACKUP_VOLUME}" | grep -c '/' || true) -ne 0 ]; then
      log_error_help \
        "BIRDHOUSE_BACKUP_VOLUME must be a named volume when running using restic -s|--snapshot." \
        "Its value indicates a directory path [BIRDHOUSE_BACKUP_VOLUME=${BIRDHOUSE_BACKUP_VOLUME}]." \
        "${BACKUP_RESTORE_USAGE}"
    fi
  fi
}

# Backup or restore component data. This is called by the do_backup_create_restore function.
# The BIRDHOUSE_BACKUP_COMMAND variable determines whether this will backup or restore.
# The first argument determines whether to run a backup or restore job (must be one of 'backup' or 'restore')
# The second argument determines which components will be backed up or restored. This argument is either a
# space delimited string containing the names of components or the string '*' (selects all enabled components).
do_component_backup_create_restore() {
  backup_restore_type="$1"
  confdirs=
  if echo " ${2} " | grep -q ' \* '; then
    confdirs="${ALL_CONF_DIRS}"
    backup_restore_all='true'
  else
    for component in ${2}; do
      component_path="$(echo "${ALL_CONF_DIRS}" | grep -o "[^[:space:]]*/${component}[[:space:]]*$")"
      if [ -n "$component_path" ]; then
        confdirs="${confdirs} ${component_path}"
      else
        log WARN "Skipping ${BIRDHOUSE_BACKUP_COMMAND} for component '${component}'. This component is not enabled."
      fi
    done
  fi
  log DEBUG "Running component backup/restore [${backup_restore_type}] for components: [${confdirs}]"
  for confdir in ${confdirs}; do
    backup_script="$(cd "${COMPOSE_DIR}"; readlink -f "${confdir}/backup" 2>/dev/null || realpath "${confdir}/backup" 2>/dev/null)"
    if [ -n "${backup_script}" ] && [ -f "${backup_script}" ]; then
      (  # run this in a subprocess so that sourcing the backup_script does not modify this environment
       . "${backup_script}"
       . "${COMPOSE_DIR}/scripts/backup.include.sh"
       $(command -v ${BIRDHOUSE_BACKUP_COMMAND}_${backup_restore_type} || true)
      )
    elif [ "${backup_restore_all}" != 'true' ]; then
      log WARN "Skipping ${BIRDHOUSE_BACKUP_COMMAND} for component '$(basename "${component}")'. No ${BIRDHOUSE_BACKUP_COMMAND} command exists."
    fi
  done
}

# Backup or restore data.
# The BIRDHOUSE_BACKUP_COMMAND variable determines whether this will backup or restore.
# The BIRDHOUSE_BACKUP_APPLICATION_COMPONENTS variable determines which component's application data will be affected.
# The BIRDHOUSE_BACKUP_USER_COMPONENTS variable determines which component's user data will be affected.
# The BIRDHOUSE_BACKUP_LOGS_COMPONENTS variable determines which component's application logs will be affected.
# The BIRDHOUSE_BACKUP_REPRESENTATIVE_COMPONENTS variable determines which component's representative data will be affected.
# The BIRDHOUSE_BACKUP_LOGS variable determines whether the birdhouse and docker logs will be affected
# The BIRDHOUSE_BACKUP_LOCAL_ENV_FILE variable determines whether the local environment file will be affected
# If BIRDHOUSE_BACKUP_DRY_RUN is true, only print a report of what will happen instead of actually backing up or restoring data.
do_backup_create_restore() {
  if [ "$BIRDHOUSE_BACKUP_DRY_RUN" = "true" ]; then
    echo "Dry-run: this would run ${BIRDHOUSE_BACKUP_COMMAND} jobs for the following:"
  elif [ "${BIRDHOUSE_BACKUP_NO_RESTIC}" != "true" ]; then
    # start with a clean backup volume so that snapshots only contain the data that is requested to be backed up/restored
    docker volume rm "${BIRDHOUSE_BACKUP_VOLUME}"
  fi

  do_component_backup_create_restore application "${BIRDHOUSE_BACKUP_APPLICATION_COMPONENTS}"
  do_component_backup_create_restore user "${BIRDHOUSE_BACKUP_USER_COMPONENTS}"
  do_component_backup_create_restore logs "${BIRDHOUSE_BACKUP_LOGS_COMPONENTS}"
  do_component_backup_create_restore representative "${BIRDHOUSE_BACKUP_REPRESENTATIVE_COMPONENTS}"

  [ "${BIRDHOUSE_BACKUP_LOGS}" = "true" ] && (. "${COMPOSE_DIR}/scripts/backup"; . "${COMPOSE_DIR}/scripts/backup.include.sh"; ${BIRDHOUSE_BACKUP_COMMAND}_logs)
  [ "${BIRDHOUSE_BACKUP_LOCAL_ENV_FILE}" = "true" ] && (. "${COMPOSE_DIR}/scripts/backup"; . "${COMPOSE_DIR}/scripts/backup.include.sh"; ${BIRDHOUSE_BACKUP_COMMAND}_local_env_file)

  [ "$BIRDHOUSE_BACKUP_DRY_RUN" = "true" ] || BIRDHOUSE_COMPOSE_TEMPLATE_SKIP=true "${THIS_FILE}" --quiet compose restart proxy

  if [ "${BIRDHOUSE_BACKUP_COMMAND}" = 'backup' ] && [ "$BIRDHOUSE_BACKUP_DRY_RUN" != "true" ] && [ "$BIRDHOUSE_BACKUP_NO_RESTIC" != "true" ]; then
    # initialize the restic repository if it doesn't exist
    if ! do_restic cat config 2>/dev/null > /dev/null; then
      do_restic init || exit $?
    fi

    # backup the scheduler data to the repository
    do_restic --verbose backup ${BIRDHOUSE_BACKUP_RESTIC_BACKUP_ARGS} /backup || exit $?

    # forget old snapshots if a policy is enabled
    if [ -n "${BIRDHOUSE_BACKUP_RESTIC_FORGET_ARGS}" ]; then
      do_restic --verbose forget ${BIRDHOUSE_BACKUP_RESTIC_FORGET_ARGS}
      do_restic --verbose prune
    fi
  fi
}

# Variables that contain references to files or directories that need to be mounted to the
# docker container that runs the restic service.
RESTIC_FILE_VARS="
RESTIC_REPOSITORY_FILE
RESTIC_PASSWORD_FILE
RESTIC_CACERT
RESTIC_TLS_CLIENT_CERT
RESTIC_CACHE_DIR
TMPDIR
AWS_SHARED_CREDENTIALS_FILE
"

# This needs to be declared as string that can be interpreted by sh (in the extra_args function below)
# This can't be a regular function because it is called by xargs which runs the command in a subprocess and
#   functions declared in parent processes are not available to their children.
#     - (Yes in bash we can do `export -f` but this needs to work in shells other than bash)
#     - (Yes in bash we can use `while IFS= read -d ''` but this needs to work in shells other than bash)
PARSE_EXTRA_ARGS_FUNCTION_DEFINITION='
  var="${1%%=*}"
  value="${1#*=}"
  # mount repository location if specified in an env variable
  [ "${var}" = "RESTIC_REPOSITORY" ] && [ -n "${value}" ] && [ "${value#*":"}" = "${value}" ] && printf " -v ${value}:${value} "
  # mount repository location if specified in a file
  if [ "${var}" = "RESTIC_REPOSITORY_FILE" ]; then
    repo_loc="$(docker run --rm -v "${value}:/restic-file" "${BASH_IMAGE}" cat /restic-file | xargs)"
    [ -n "${repo_loc}" ] && [ "${repo_loc#*":"}" = "${repo_loc}" ] && printf " -v ${repo_loc}:${repo_loc} "
  fi
  # mount any files specified in the restic env file
  echo "${RESTIC_FILE_VARS}" | grep -q "^[[:space:]]*${var}[[:space:]]*$" && printf " -v ${value}:${value}:ro "
'

# Determine extra args that need to be added to the docker run command that runs the restic service.
extra_args() {
  env -i sh -ac '. "$1" && env -0' sh "${BIRDHOUSE_BACKUP_RESTIC_ENV_FILE}" | xargs -0 -n1 sh -c "$PARSE_EXTRA_ARGS_FUNCTION_DEFINITION" sh
}

# Run a restic command.
do_restic() {
  source_env
  restic_cmd="docker run --rm --env-file='${BIRDHOUSE_BACKUP_RESTIC_ENV_FILE}' --env 'RESTIC_PASSWORD_COMMAND=' -v '${BIRDHOUSE_BACKUP_SSH_KEY_DIR}:/root/.ssh:ro' -v ${BIRDHOUSE_BACKUP_VOLUME}:/backup ${BIRDHOUSE_BACKUP_RESTIC_EXTRA_DOCKER_OPTIONS} $(extra_args) ${RESTIC_IMAGE} $@"
  log DEBUG "Running restic command: ${restic_cmd}"
  eval "${restic_cmd}"
}

parse_backup_args() {
  case "$1" in
    -h|--help)
      echo "${BACKUP_HELP}" | more
      exit
      ;;
    create)
      shift
      parse_backup_create_args "$@"
      ;;
    restore)
      shift
      parse_backup_restore_args "$@"
      ;;
    restic)
      shift
      do_restic "$@"
      ;;
    *)
      >&2 echo "$@"
      >&2 echo "$BACKUP_USAGE"
      exit 1
      ;;
  esac
}

# Parse args that are common to all backup commands
parse_backup_common_args() {
  calling_func="$1"
  shift
  case "$1" in
    -d|--dry-run)
      shift
      export BIRDHOUSE_BACKUP_DRY_RUN=true # export needed so that it is visible in subprocess when actually calling backup scripts
      ${calling_func} "$@"
      ;;
    -a|--component-application-data)
      shift
      BIRDHOUSE_BACKUP_APPLICATION_COMPONENTS="${BIRDHOUSE_BACKUP_APPLICATION_COMPONENTS} ${1}"
      shift
      ${calling_func} "$@"
      ;;
    -u|--component-user-data)
      shift
      BIRDHOUSE_BACKUP_USER_COMPONENTS="${BIRDHOUSE_BACKUP_USER_COMPONENTS} ${1}"
      shift
      ${calling_func} "$@"
      ;;
    -l|--component-logs-data)
      shift
      BIRDHOUSE_BACKUP_LOGS_COMPONENTS="${BIRDHOUSE_BACKUP_LOGS_COMPONENTS} ${1}"
      shift
      ${calling_func} "$@"
      ;;
    -r|--representative-data)
      shift
      BIRDHOUSE_BACKUP_REPRESENTATIVE_COMPONENTS="${BIRDHOUSE_BACKUP_REPRESENTATIVE_COMPONENTS} ${1}"
      shift
      ${calling_func} "$@"
      ;;
    --birdhouse-logs)
      shift
      BIRDHOUSE_BACKUP_LOGS=true
      ${calling_func} "$@"
      ;;
    --local-env-file)
      shift
      BIRDHOUSE_BACKUP_LOCAL_ENV_FILE=true
      ${calling_func} "$@"
      ;;
    -??*)
      parse_multiple_short_flags ${calling_func} "$@"
      ;;
    *)
      return 1
      ;;
  esac
}

# Parse backup create args
parse_backup_create_args() {
  case "$1" in
    -h|--help)
      echo "${BACKUP_CREATE_HELP}" | more
      exit
      ;;
    --no-restic)
      shift
      export BIRDHOUSE_BACKUP_NO_RESTIC=true
      parse_backup_create_args "$@"
      ;;
    '')
      BIRDHOUSE_BACKUP_COMMAND=backup
      source_env
      check_backup_create_args
      ensure_birdhouse_running
      do_backup_create_restore
      exit
      ;;
    *)
      if ! parse_backup_common_args parse_backup_create_args "$@"; then
        >&2 echo "$@"
        >&2 echo "$BACKUP_CREATE_USAGE"
        exit 1
      fi
      ;;
    esac
}

# Parse backup restore args
parse_backup_restore_args() {
  case "$1" in
    -h|--help)
      echo "${BACKUP_RESTORE_HELP}" | more
      exit
      ;;
    -s|--snapshot)
      shift
      BIRDHOUSE_BACKUP_RESTORE_SNAPSHOT="$1"
      shift
      parse_backup_restore_args "$@"
      ;;
    --no-restic)
      shift
      export BIRDHOUSE_BACKUP_NO_RESTIC=true
      parse_backup_restore_args "$@"
      ;;
    --no-clobber)
      shift
      export BIRDHOUSE_BACKUP_RESTORE_NO_CLOBBER=true
      parse_backup_restore_args "$@"
      ;;
    '')
      BIRDHOUSE_BACKUP_COMMAND=restore
      source_env
      check_backup_restore_args
      ensure_birdhouse_running
      do_backup_create_restore
      exit
      ;;
    *)
      if ! parse_backup_common_args parse_backup_restore_args "$@"; then
        >&2 echo "$@"
        >&2 echo "$BACKUP_RESTORE_USAGE"
        exit 1
      fi
      ;;
    esac
}

# Echos "True" if the first argument is a valid log level
check_log_dest_override() {
  case "$1" in
    DEBUG|INFO|WARN|ERROR|CRITICAL)
      echo True
    ;;
  esac
}

# Parse arguments and options
parse_args() {
  case "$1" in
    -b|--backwards-compatible)
      shift
      if [ "${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED+set}" = 'set' ]; then
        BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED_PREV="${BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED}"
      else
        BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED_UNSET="True"
      fi
      export BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED="True"  # The argument here takes precedence over the env variable
      parse_args "$@"
      ;;
    -e=*|--env-file=*)
      arg_value="${1#*=}"
      shift
      parse_args -e "${arg_value}" "$@"
      ;;
    -e|--env-file)
      shift
      if [ "${BIRDHOUSE_LOCAL_ENV+set}" = 'set' ]; then
        BIRDHOUSE_LOCAL_ENV_PREV="${BIRDHOUSE_LOCAL_ENV}"
      else
        BIRDHOUSE_LOCAL_ENV_UNSET="True"
      fi
      BIRDHOUSE_LOCAL_ENV=$(realpath "$1")  # The argument here takes precedence over the env variable
      export BIRDHOUSE_LOCAL_ENV
      shift
      parse_args "$@"
      ;;
    -q|--quiet)
      shift
      if [ "$(check_log_dest_override "$1")" ]; then
        export BIRDHOUSE_LOG_DEST_OVERRIDE="${BIRDHOUSE_LOG_DEST_OVERRIDE}:$1:quiet:"
        shift
      else
        export BIRDHOUSE_LOG_QUIET=True # The argument here takes precedence over the env variable
      fi
      parse_args "$@"
      ;;
    -s|--log-stdout)
      shift
      if [ "$(check_log_dest_override "$1")" ]; then
        export BIRDHOUSE_LOG_DEST_OVERRIDE="${BIRDHOUSE_LOG_DEST_OVERRIDE}:$1:fd:1"
        shift
      else
        export BIRDHOUSE_LOG_FD=1 # The argument here takes precedence over the env variable
      fi
      parse_args "$@"
      ;;
    -l=*|--log-file=*)
      arg_value="${1#*=}"
      shift
      parse_args --log-file "${arg_value}" "$@"
      ;;
    -l|--log-file)
      shift
      # Note: cannot log to a file named DEBUG, INFO, WARN, ERROR, or CRITICAL
      if [ "$(check_log_dest_override "$1")" ]; then
        export BIRDHOUSE_LOG_DEST_OVERRIDE="${BIRDHOUSE_LOG_DEST_OVERRIDE}:$1:file:$(realpath -- "$2")"
        shift
      else
        export BIRDHOUSE_LOG_FILE=$(realpath -- "$1") # The argument here takes precedence over the env variable
      fi
      shift
      parse_args "$@"
      ;;
    -L=*|--log-level=*)
      arg_value="${1#*=}"
      shift
      parse_args --log-level "${arg_value}" "$@"
      ;;
    -L|--log-level)
      shift
      export BIRDHOUSE_LOG_LEVEL="$1" # The argument here takes precedence over the env variable
      shift
      parse_args "$@"
      ;;
    info)
      shift
      "${BIRDHOUSE_COMPOSE}" info "$@"
      exit $?
      ;;
    compose)
      shift
      "${BIRDHOUSE_COMPOSE}" "$@"
      exit $?
      ;;
    configs)
      shift
      parse_configs_args "$@"
      ;;
    backup)
      shift
      parse_backup_args "$@"
      ;;
    -h|--help)
      echo "$HELP" | more
      ;;
    -??*)
      parse_multiple_short_flags parse_args "$@"
      ;;
    *)
      >&2 echo "$@"
      >&2 echo "$USAGE"
      exit 1
      ;;
  esac
}

parse_args "$@"
