#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/internal-functions"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x

cmd-letsencrypt-active() {
  declare desc="Verify if letsencrypt is active for an app"
  declare cmd="letsencrypt:active"
  [[ "$1" == "$cmd" ]] && shift 1

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"

  verify_app_name "$APP"
  fn-letsencrypt-is-active "$APP"
}

cmd-letsencrypt-auto-renew() {
  declare desc="auto-renew certificates if necessary"
  declare cmd="letsencrypt:auto-renew"
  [[ "$1" == "$cmd" ]] && shift 1

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"
  local expiry grace_period

  if [ -z "$APP" ]; then
    dokku_log_info2 "Auto-renewing all apps..."
    local EXIT_CODE=0

    # For all apps, sorted by ascending time left until renewal.
    # This way, we'll prioritize apps that need to be renewed soon
    # if we should hit a rate limit along the way.
    # Store the list in a temporary file to avoid subshell issues with pipelines
    local temp_file=$(mktemp)
    fn-letsencrypt-list-apps-with-expiry | sort -nk5 > "$temp_file"
    
    while IFS=$'\t' read -r -a appExpiry; do
      if [[ ${appExpiry[4]} -lt 0 ]]; then
        dokku_log_info1 "${appExpiry[0]} needs renewal"
        if ! dokku letsencrypt:enable "${appExpiry[0]}"; then
          EXIT_CODE=1
        fi
      else
        days_left=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
        dokku_log_verbose "${appExpiry[0]} still has $days_left days left before renewal"
      fi
    done < "$temp_file"
    
    rm -f "$temp_file"

    dokku_log_info2 "Finished auto-renewal"
    if [[ "$EXIT_CODE" != 0 ]]; then
      dokku_log_fail "One or more apps failed to have their certificates renewed"
    fi
  else
    verify_app_name "$APP"

    if [[ "$(fn-letsencrypt-is-active "$APP")" != "true" ]]; then
      dokku_log_info1 "Letsencrypt not enabled for ${APP}"
      return
    fi

    expiry=$(fn-letsencrypt-expiration "$APP")
    grace_period=$(fn-letsencrypt-computed-graceperiod "$APP")
    local time_to_renewal=$((expiry - grace_period - $(date +%s)))

    if [[ $time_to_renewal -lt 0 ]]; then
      dokku_log_info2 "Auto-renew ${APP}..."
      dokku letsencrypt:enable "$APP"
    else
      days_left=$(fn-letsencrypt-format-timediff $time_to_renewal)
      dokku_log_verbose "$APP still has $days_left left before renewal"
    fi

  fi
}

cmd-letsencrypt-cleanup() {
  declare desc="clean up unused certificate directories"
  declare cmd="letsencrypt:cleanup"
  [[ "$1" == "$cmd" ]] && shift 1
  local certdir_basename current_config

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"
  verify_app_name "$APP"

  local app_root="$DOKKU_ROOT/$APP"
  local le_root="$app_root/letsencrypt"

  current_config="$(basename "$(readlink "$le_root/certs/current")")"

  if [ -z "$current_config" ] || [[ ! -d "$le_root/certs/$current_config" ]]; then
    dokku_log_warn "Cannot resolve the 'current' certificate directory!"
    return 1
  fi

  dokku_log_info2 "Cleaning up stale certificate directories for $APP"
  dokku_log_info1 "  - current config hash $current_config"

  for certdir in $le_root/certs/*; do
    certdir_basename="$(basename "$certdir")"

    if [[ "$certdir_basename" == "current" ]] || [[ "$certdir_basename" == "$current_config" ]]; then continue; fi
    dokku_log_info1 "  - stale directory $certdir_basename"

    rm -rf "$le_root/certs/$certdir_basename"
  done
}

cmd-letsencrypt-cron-job() {
  declare desc="Add or remove a cron job that periodically calls auto-renew"
  declare cmd="letsencrypt:cron-job"
  [[ "$1" == "$cmd" ]] && shift 1
  declare FLAG="$1"

  if [[ "$FLAG" == "--add" ]]; then
    fn-letsencrypt-cron-job-add
  elif [[ "$FLAG" == "--remove" ]]; then
    fn-letsencrypt-cron-job-remove
  else
    dokku_log_verbose "Specify --add or --remove to modify the cron-job"
  fi
}

cmd-letsencrypt-disable() {
  declare desc="Disable letsencrypt for an app"
  declare cmd="letsencrypt:disable"
  [[ "$1" == "$cmd" ]] && shift 1

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"
  verify_app_name "$APP"

  dokku_log_info1 "Disabling letsencrypt for app"
  local le_root="$DOKKU_ROOT/$APP/letsencrypt"
  local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls"

  dokku_log_verbose "Removing letsencrypt files for $APP"
  rm -rf "$le_root"

  dokku_log_verbose "Removing SSL endpoint from $APP"
  rm -rf "$APP_SSL_PATH"
  plugn trigger post-certs-remove "$APP"
  plugn trigger post-domains-update "$APP"

  dokku_log_info1 "Done"
}

cmd-letsencrypt-enable() {
  declare desc="Enable or renew letsencrypt for an app"
  declare cmd="letsencrypt:enable"
  [[ "$1" == "$cmd" ]] && shift 1

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"
  if [[ "$APP" == "--all" ]]; then
    for app in $(dokku_apps); do
      fn-letsencrypt-enable "$app"
    done
  else
    fn-letsencrypt-enable "$APP"
  fi
}

cmd-letsencrypt-list() {
  declare desc="list letsencrypt-secured apps and certificate expiries"
  declare cmd="letsencrypt:list"
  [[ "$1" == "$cmd" ]] && shift 1

  dokku_col_log_info1_quiet "App name" "Certificate Expiry" "Time before expiry" "Time before renewal"

  fn-letsencrypt-list-apps-with-expiry \
    | sort -nk2 \
    | while IFS=$'\t' read -r -a appExpiry; do
      expire_date=$(date -d "@${appExpiry[1]}" +"%F %T")
      expire_time=$(fn-letsencrypt-format-timediff "${appExpiry[3]}")
      renew_time=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
      dokku_col_log_msg "${appExpiry[0]}" "${expire_date}" "${expire_time}" "${renew_time}"
    done
}

cmd-letsencrypt-report() {
  declare desc="displays a letsencrypt report for one or more apps"
  declare cmd="letsencrypt:report"
  [[ "$1" == "$cmd" ]] && shift 1
  declare APP="$1" INFO_FLAG="$2"
  local INSTALLED_APPS
  INSTALLED_APPS=$(dokku_apps)

  if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
    INFO_FLAG="$APP"
    APP=""
  fi

  if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
    INFO_FLAG="true"
  fi

  if [[ -z "$APP" ]]; then
    for app in $INSTALLED_APPS; do
      cmd-letsencrypt-report-single "$app" "$INFO_FLAG" | tee || true
    done
  else
    cmd-letsencrypt-report-single "$APP" "$INFO_FLAG"
  fi
}

cmd-letsencrypt-report-single() {
  declare APP="$1" INFO_FLAG="$2"
  if [[ "$INFO_FLAG" == "true" ]]; then
    INFO_FLAG=""
  fi
  verify_app_name "$APP"
  local flag_map=(
    "--letsencrypt-active: $(fn-letsencrypt-is-active "$APP")"
    "--letsencrypt-autorenew: $(fn-letsencrypt-is-autorenew-enabled "$APP")"
    "--letsencrypt-computed-dns-provider: $(fn-letsencrypt-computed-dns-provider "$APP")"
    "--letsencrypt-global-dns-provider: $(fn-letsencrypt-global-dns-provider)"
    "--letsencrypt-dns-provider: $(fn-letsencrypt-dns-provider "$APP")"
    "--letsencrypt-computed-email: $(fn-letsencrypt-computed-email "$APP")"
    "--letsencrypt-global-email: $(fn-letsencrypt-global-email)"
    "--letsencrypt-email: $(fn-letsencrypt-email "$APP")"
    "--letsencrypt-expiration: $(fn-letsencrypt-expiration "$APP")"
    "--letsencrypt-computed-graceperiod: $(fn-letsencrypt-computed-graceperiod "$APP")"
    "--letsencrypt-global-graceperiod: $(fn-letsencrypt-global-graceperiod)"
    "--letsencrypt-graceperiod: $(fn-letsencrypt-graceperiod "$APP")"
    "--letsencrypt-computed-lego-docker-args: $(fn-letsencrypt-computed-lego-docker-args "$APP")"
    "--letsencrypt-global-lego-docker-args: $(fn-letsencrypt-global-lego-docker-args)"
    "--letsencrypt-lego-docker-args: $(fn-letsencrypt-lego-docker-args "$APP")"
    "--letsencrypt-computed-server: $(fn-letsencrypt-computed-server "$APP")"
    "--letsencrypt-global-server: $(fn-letsencrypt-global-server)"
    "--letsencrypt-server: $(fn-letsencrypt-server "$APP")"
  )

  if [[ -z "$INFO_FLAG" ]]; then
    dokku_log_info2_quiet "${APP} letsencrypt information"
    for flag in "${flag_map[@]}"; do
      key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
      dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
    done
  else
    local match=false
    local value_exists=false
    for flag in "${flag_map[@]}"; do
      valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
      if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
        value=${flag#*: }
        size="${#value}"
        if [[ "$size" -ne 0 ]]; then
          echo "$value" && match=true && value_exists=true
        else
          match=true
        fi
      fi
    done
    [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
    [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
  fi
}

cmd-letsencrypt-revoke() {
  declare desc="Revoke a certificate"
  declare cmd="letsencrypt:revoke"
  [[ "$1" == "$cmd" ]] && shift 1

  # Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
  [[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@

  declare APP="$1"
  verify_app_name "$APP"

  dokku_log_info2 "Revoke letsencrypt certificate from ${APP}..."

  fn-letsencrypt-check-email "$APP"
  fn-letsencrypt-acme-revoke "$APP" || true

  dokku_log_info1 "Done"
}

cmd-letsencrypt-set() {
  declare desc="set or clear a letsencrypt property for an app"
  declare cmd="letsencrypt:set"
  [[ "$1" == "$cmd" ]] && shift 1
  declare APP="$1" KEY="$2" VALUE="$3"
  local VALID_KEYS=("dns-provider" "email" "graceperiod" "server" "lego-docker-args")
  [[ "$APP" == "--global" ]] || verify_app_name "$APP"

  [[ -z "$KEY" ]] && dokku_log_fail "No key specified"

  if ! fn-in-array "$KEY" "${VALID_KEYS[@]}" && [[ "$KEY" != dns-provider-* ]]; then
    dokku_log_fail "Invalid key specified, valid keys include: dns-provider, dns-provider-*, email, graceperiod, server, lego-docker-args"
  fi

  if [[ -n "$VALUE" ]]; then
    dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}"
    fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
  else
    dokku_log_info2_quiet "Unsetting ${KEY}"
    if [[ "$KEY" == "rev-env-var" ]]; then
      fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
    else
      fn-plugin-property-delete "letsencrypt" "$APP" "$KEY"
      if [[ "$KEY" == "enabled" ]]; then
        fn-plugin-property-destroy "letsencrypt" "$APP"
      fi
    fi
  fi
}
