#!/usr/bin/env bash
# One command that brings up a GlobalArt on-premises installation: the licence
# agent, herald and the products listed in PRODUCTS. Re-running it upgrades an
# installation in place and keeps its data and the secrets it generated.
set -euo pipefail

CONFIG="${1:-install.env}"
if [ ! -f "$CONFIG" ]; then
  echo "usage: $0 <config file>    (start from install.env.example)" >&2
  exit 2
fi
# shellcheck disable=SC1090
. "$CONFIG"

CHART_REPO="${CHART_REPO:-https://selfhosted.globalart.dev/charts}"
CHART_VERSION="${CHART_VERSION:-}"
LICENSING_NAMESPACE="${LICENSING_NAMESPACE:-globalart-licensing}"
HERALD_NAMESPACE="${HERALD_NAMESPACE:-herald}"
HERALD_RELEASE="${HERALD_RELEASE:-herald}"
LICENCE_SERVICE="${LICENCE_SERVICE:-licence-agent}"
PRODUCTS="${PRODUCTS:-ownlate}"
REGISTRY_USER="${REGISTRY_USER:-}"
REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-}"
REGISTRY_HOST="${REGISTRY_HOST:-selfhosted.globalart.dev}"
WAIT_TIMEOUT="${WAIT_TIMEOUT:-15m}"

say() { printf '\n\033[1m==> %s\033[0m\n' "$1"; }
die() { printf '\033[31merror: %s\033[0m\n' "$1" >&2; exit 1; }

for tool in kubectl helm; do
  command -v "$tool" >/dev/null 2>&1 || die "$tool is not installed"
done
kubectl version >/dev/null 2>&1 || die "kubectl cannot reach a cluster (check KUBECONFIG)"

# helm splits a --set value on an unescaped comma, so a comma inside a value has
# to be escaped before it ever reaches helm.
helm_value() { printf '%s' "$1" | sed 's/,/\\,/g'; }

ensure_namespace() {
  kubectl get namespace "$1" >/dev/null 2>&1 || kubectl create namespace "$1" >/dev/null
  # The pull secret matters only while the registry is not public.
  if [ -n "$REGISTRY_USER" ]; then
    kubectl -n "$1" create secret docker-registry nexus-pull \
      --docker-server="$REGISTRY_HOST" \
      --docker-username="$REGISTRY_USER" \
      --docker-password="$REGISTRY_PASSWORD" \
      --dry-run=client -o yaml | kubectl apply -f - >/dev/null
  fi
}

# Everything an installation shares, set once so a product chart never repeats
# the licence agent address or where herald lives.
common_sets() {
  COMMON=(
    --set "licence.agentUrl=http://$LICENCE_SERVICE.$LICENSING_NAMESPACE.svc.cluster.local:8080"
    --set "herald.namespace=$HERALD_NAMESPACE"
    --set "herald.release=$HERALD_RELEASE"
  )
  [ -z "$REGISTRY_USER" ] && COMMON+=(--set "imagePullSecrets=null")
  return 0
}

ownlate_sets() {
  [ -n "${OWNLATE_APP_HOST:-}" ] || die "OWNLATE_APP_HOST is required for the ownlate product"
  [ -n "${OWNLATE_API_HOST:-}" ] || die "OWNLATE_API_HOST is required for the ownlate product"
  SETS=(
    --set "hosts.app=$OWNLATE_APP_HOST"
    --set "hosts.api=$OWNLATE_API_HOST"
    --set "scheme=${OWNLATE_SCHEME:-https}"
  )
  [ -n "${INGRESS_CLASS:-}" ] && SETS+=(--set "ingress.className=$INGRESS_CLASS")
  [ -n "${OWNLATE_TLS_SECRET:-}" ] && SETS+=(--set "ingress.tlsSecretName=$OWNLATE_TLS_SECRET")
  [ -n "${OWNLATE_ADMIN_EMAIL:-}" ] && SETS+=(--set "auth.bootstrapAdmin.email=$(helm_value "$OWNLATE_ADMIN_EMAIL")")
  if [ -n "${OWNLATE_OIDC_ISSUER:-}" ]; then
    SETS+=(
      --set "auth.oidc.issuerUrl=$(helm_value "$OWNLATE_OIDC_ISSUER")"
      --set "auth.oidc.clientId=$(helm_value "${OWNLATE_OIDC_CLIENT_ID:-}")"
      --set-string "auth.oidc.clientSecret=$(helm_value "${OWNLATE_OIDC_CLIENT_SECRET:-}")"
      --set "auth.oidc.name=$(helm_value "${OWNLATE_OIDC_NAME:-SSO}")"
    )
    [ "${OWNLATE_OIDC_AUTO_PROVISION:-false}" = "true" ] && SETS+=(--set "auth.oidc.autoProvision=true")
  fi
  if [ "${OWNLATE_S3_ENABLED:-false}" = "true" ]; then
    SETS+=(
      --set "storage.s3.enabled=true"
      --set "storage.s3.endpoint=$(helm_value "$OWNLATE_S3_ENDPOINT")"
      --set "storage.s3.bucket=$(helm_value "$OWNLATE_S3_BUCKET")"
      --set-string "storage.s3.accessKeyId=$(helm_value "$OWNLATE_S3_ACCESS_KEY")"
      --set-string "storage.s3.secretAccessKey=$(helm_value "$OWNLATE_S3_SECRET_KEY")"
    )
  fi
  return 0
}

product_sets() {
  case "$1" in
    ownlate) ownlate_sets ;;
    *) die "no installer mapping for the product $1" ;;
  esac
}

install_release() {
  local release="$1" namespace="$2" chart="$3"; shift 3
  local version=()
  [ -n "$CHART_VERSION" ] && version=(--version "$CHART_VERSION")
  # No --wait: helm still waits for the migration and seed hook jobs, but not
  # for the app pods, which cannot be ready until those hooks populate a fresh
  # database. The rollout is awaited here instead, once the schema is in place.
  helm upgrade --install "$release" "$chart" -n "$namespace" \
    "${version[@]}" --timeout "$WAIT_TIMEOUT" "$@"
  local resource
  for resource in $(kubectl -n "$namespace" get deploy,statefulset \
    -l "app.kubernetes.io/instance=$release" -o name 2>/dev/null); do
    kubectl -n "$namespace" rollout status "$resource" --timeout "$WAIT_TIMEOUT"
  done
}

say "Checking tools and the cluster"
kubectl config current-context

say "Chart repository"
if ! helm repo list 2>/dev/null | awk '{print $1}' | grep -qx globalart; then
  helm repo add globalart "$CHART_REPO" >/dev/null
fi
helm repo update globalart >/dev/null

say "Licence agent in $LICENSING_NAMESPACE"
ensure_namespace "$LICENSING_NAMESPACE"
LICENCE_SETS=(
  --set "serviceName=$LICENCE_SERVICE"
  --set "product=${LICENCE_PRODUCT:-ownlate}"
)
if [ -n "${LICENCE_KEY:-}" ]; then
  LICENCE_SETS+=(--set-string "licence.key=$LICENCE_KEY")
elif [ -n "${LICENCE_FILE:-}" ]; then
  [ -f "$LICENCE_FILE" ] || die "LICENCE_FILE $LICENCE_FILE does not exist"
  LICENCE_SETS+=(--set-file "licence.token=$LICENCE_FILE")
fi
[ -z "$REGISTRY_USER" ] && LICENCE_SETS+=(--set "imagePullSecrets=null")
install_release licence "$LICENSING_NAMESPACE" globalart/license-agent "${LICENCE_SETS[@]}"

say "Waiting for a licence in force"
agent="http://$LICENCE_SERVICE.$LICENSING_NAMESPACE.svc.cluster.local:8080/v1/licence"
code=""
for _ in $(seq 1 30); do
  code=$(kubectl -n "$LICENSING_NAMESPACE" run "licence-probe-$RANDOM" --rm -i --quiet --restart=Never \
    --image=curlimages/curl:8.11.1 --command -- \
    sh -c "curl -s -o /dev/null -w '%{http_code}' $agent" 2>/dev/null | tr -dc '0-9' | tail -c 3 || true)
  [ "$code" = "200" ] && { echo "the licence agent holds a licence"; break; }
  echo "the agent has no licence yet ($code), retrying"
  sleep 6
done
installation=$(kubectl -n "$LICENSING_NAMESPACE" get secret licence-installation -o jsonpath='{.data.id}' 2>/dev/null | base64 -d || true)
[ -n "$installation" ] && echo "installation id: $installation"
[ "$code" = "200" ] || echo "WARNING: no licence in force yet; the products wait for it and will not start until there is one."

say "Herald in $HERALD_NAMESPACE"
ensure_namespace "$HERALD_NAMESPACE"
common_sets
HERALD_SETS=("${COMMON[@]}")
index=0
for product in $PRODUCTS; do
  HERALD_SETS+=(--set "seed.products[$index]=$product")
  index=$((index + 1))
done
if [ -n "${SMTP_HOST:-}" ]; then
  [ -n "${SMTP_FROM:-}" ] || die "SMTP_FROM is required with SMTP_HOST"
  HERALD_SETS+=(
    --set "smtp.host=$SMTP_HOST"
    --set "smtp.port=${SMTP_PORT:-587}"
    --set "smtp.from=$(helm_value "$SMTP_FROM")"
    --set "smtp.user=$(helm_value "${SMTP_USER:-}")"
    --set-string "smtp.password=$(helm_value "${SMTP_PASSWORD:-}")"
  )
  [ "${SMTP_SECURE:-false}" = "true" ] && HERALD_SETS+=(--set "smtp.secure=true")
fi
[ -n "${COMPANY_NAME:-}" ] && HERALD_SETS+=(--set "branding.companyName=$(helm_value "$COMPANY_NAME")")
[ -n "${COMPANY_ADDRESS:-}" ] && HERALD_SETS+=(--set "branding.companyAddress=$(helm_value "$COMPANY_ADDRESS")")
install_release "$HERALD_RELEASE" "$HERALD_NAMESPACE" globalart/herald "${HERALD_SETS[@]}"

for product in $PRODUCTS; do
  namespace_var="$(echo "$product" | tr '[:lower:]-' '[:upper:]_')_NAMESPACE"
  namespace="${!namespace_var:-$product}"
  say "Product $product in $namespace"
  ensure_namespace "$namespace"
  common_sets
  product_sets "$product"
  install_release "$product" "$namespace" "globalart/$product" "${COMMON[@]}" "${SETS[@]}"
done

say "Done"
for product in $PRODUCTS; do
  namespace_var="$(echo "$product" | tr '[:lower:]-' '[:upper:]_')_NAMESPACE"
  namespace="${!namespace_var:-$product}"
  echo
  helm get notes "$product" -n "$namespace" 2>/dev/null || true
done
