#!/usr/bin/env bash
set -e

# -----------------------------------------------------------------------------
# Deploy rtgs-signing (savy) to an AWS EKS cluster (e.g. rtgs-pp-test).
#
# Targets your CURRENT kube context (or $CONTEXT) and conditionally installs
# the cloud prerequisites:
#   * ingress-nginx      (provisions an AWS load balancer)
#   * metrics-server     (usually already present on EKS; installed only if absent)
#   * CloudNativePG      (postgres operator that backs the wallet store)
#   * cert-manager       (+ a letsencrypt-prod ClusterIssuer for automatic TLS)
# Each is skipped if already installed, so re-runs are cheap and idempotent.
#
# Required env:
#   ACME_EMAIL   Let's Encrypt account email for the cert-manager ClusterIssuer.
#
# Optional env (or positional args):
#   RELEASE      helm release name        (default: rtgs-signing)                  [arg 1]
#   NAMESPACE    target namespace         (default: rtgs-signing)                  [arg 2]
#   VALUES       EKS values file          (default: ./helm/rtgs-signing/values-savy-eks.yaml) [arg 3]
#   CONTEXT          kube context to target (default: current context)
#   CHART_VERSION    chart version to deploy (default: latest in the repo)
#   CHART_REPO       helm repo URL          (default: https://rtgsgateway.blob.core.windows.net/helm-repo/)
#   ROUTE53_ZONE_ID  Route 53 hosted zone id for the ingress hosts (default: auto-detected
#                    from the host domain; DNS registration is skipped if none is found).
#
# NOTE: the committed values-savy-eks.yaml is a REDACTED example (placeholders for
# secrets/hosts). Copy it, fill in the real values, and point VALUES at your copy —
# the script refuses to deploy a file that still contains <placeholders>.
# -----------------------------------------------------------------------------

# ------
# SETUP:
# ------
this=$(basename "${BASH_SOURCE[0]}")
name=${RELEASE:-${1:-rtgs-signing}}
ns=${NAMESPACE:-${2:-rtgs-signing}}
values=${VALUES:-${3:-./helm/rtgs-signing/values-savy-eks.yaml}}
context=${CONTEXT:-$(kubectl config current-context)}
acme_email=${ACME_EMAIL:-}
chart_version=${CHART_VERSION:-}
chart_repo=${CHART_REPO:-https://rtgsgateway.blob.core.windows.net/helm-repo/}

# Resolve a relative values path to absolute so it survives any internal dir changes.
case "$values" in /*) ;; *) values="$(pwd)/$values" ;; esac

kctx=(--context "$context")
hctx=(--kube-context "$context")

# -----------
# GUARDRAILS:
# -----------
# This script is for cloud/EKS — refuse to run against an obviously-local context.
case "$context" in
    docker-desktop|kind-*|*minikube*|*rancher-desktop*)
        echo "ERROR: context '$context' looks local. This script targets EKS."
        echo "       Set CONTEXT=<eks-context> to target a cloud cluster."
        exit 1
        ;;
esac

if [[ -z "$acme_email" ]]; then
    echo "ERROR: ACME_EMAIL is required (Let's Encrypt account email for the ClusterIssuer)."
    echo "       e.g. ACME_EMAIL=you@rtgs.com ./$this"
    exit 1
fi

echo "Context      : $context"
echo "Release      : $name"
echo "Namespace    : $ns"
echo "Values       : $values"
echo "ACME email   : $acme_email"
echo "Chart repo   : $chart_repo"
echo "Chart version: ${chart_version:-latest}"
echo

# ----------
# FUNCTIONS:
# ----------

# enable ingress routing (creates an AWS load balancer via the nginx controller)
function install_nginx {
    if helm "${hctx[@]}" status ingress-nginx -n ingress-nginx >/dev/null 2>&1; then
        echo "Skipping ingress-nginx (already installed)."
    else
        echo "Installing ingress-nginx..."
        helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx >/dev/null 2>&1 || true
        helm repo update >/dev/null
        helm "${hctx[@]}" upgrade --install --wait \
            --namespace ingress-nginx --create-namespace \
            ingress-nginx ingress-nginx/ingress-nginx
    fi
    echo
}

# install metrics-server (EKS usually ships this as an addon)
function install_metrics_server {
    if kubectl "${kctx[@]}" get deployment metrics-server -n kube-system >/dev/null 2>&1; then
        echo "Skipping metrics-server (already installed)."
    else
        echo "Installing metrics-server..."
        kubectl "${kctx[@]}" apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
        kubectl "${kctx[@]}" rollout status deployment/metrics-server -n kube-system
    fi
    echo
}

# install CloudNativePG operator (backs the postgres wallet store)
function install_cnpg {
    if helm "${hctx[@]}" status cnpg -n cnpg-system >/dev/null 2>&1; then
        echo "Skipping CloudNativePG (already installed)."
    else
        echo "Installing CloudNativePG operator..."
        helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null 2>&1 || true
        helm repo update >/dev/null
        helm "${hctx[@]}" upgrade --install --wait \
            --namespace cnpg-system --create-namespace \
            cnpg cnpg/cloudnative-pg
    fi
    echo
}

# install cert-manager (+CRDs) for automatic Let's Encrypt TLS
function install_cert_manager {
    if helm "${hctx[@]}" status cert-manager -n cert-manager >/dev/null 2>&1; then
        echo "Skipping cert-manager (already installed)."
    else
        echo "Installing cert-manager..."
        helm repo add jetstack https://charts.jetstack.io >/dev/null 2>&1 || true
        helm repo update >/dev/null
        helm "${hctx[@]}" upgrade --install --wait \
            --namespace cert-manager --create-namespace \
            --set crds.enabled=true \
            cert-manager jetstack/cert-manager
    fi
    # the webhook must be up before any ClusterIssuer/Certificate is accepted
    kubectl "${kctx[@]}" -n cert-manager rollout status deploy/cert-manager-webhook --timeout=120s
    echo
}

# create the letsencrypt-prod ClusterIssuer referenced by tls.clusterIssuer
function ensure_cluster_issuer {
    if kubectl "${kctx[@]}" get clusterissuer letsencrypt-prod >/dev/null 2>&1; then
        echo "Skipping ClusterIssuer letsencrypt-prod (already exists)."
    else
        echo "Creating ClusterIssuer letsencrypt-prod (email: $acme_email)..."
        kubectl "${kctx[@]}" apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: $acme_email
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            class: nginx
EOF
    fi
    echo
}

# deploy / upgrade the rtgs-signing chart from the published helm repo
function helm_install {
    if [[ ! -f "$values" ]]; then
        echo "ERROR: values file \"$values\" not found (set VALUES=path/to/your.yaml)."
        exit 1
    fi
    if grep -qE '<[^>]+>' "$values"; then
        echo "ERROR: \"$values\" still contains <placeholder> values — it looks like the"
        echo "       redacted example. Copy it, fill in the real secrets/hosts, then:"
        echo "         VALUES=./helm/rtgs-signing/values-savy-eks.<cluster>.yaml ./$this"
        exit 1
    fi
    echo "Adding/updating helm repo rtgs-signing -> $chart_repo"
    helm repo add rtgs-signing "$chart_repo" >/dev/null 2>&1 || true
    helm repo update rtgs-signing >/dev/null
    echo "Deploying chart rtgs-signing/$name as release \"$name\" into namespace \"$ns\"..."
    local version_args=()
    [[ -n "$chart_version" ]] && version_args=(--version "$chart_version")
    helm "${hctx[@]}" upgrade --install --wait \
        --namespace "$ns" --create-namespace \
        --values "$values" \
        "${version_args[@]}" \
        "$name" \
        "rtgs-signing/$name"
    echo
}

# register public DNS: an alias-A record per ingress host -> the nginx ELB (Route 53).
# Opt-in — skipped unless a Route 53 hosted zone can be resolved for the ingress hosts.
# Needed so the hostnames resolve to the load balancer AND so cert-manager's HTTP-01
# challenge can complete and issue the Let's Encrypt certificates.
function register_dns {
    local elb="" hosts zone_id elb_zone h i

    # wait for the load balancer hostname (fresh clusters provision it asynchronously)
    for i in $(seq 1 30); do
        elb=$(kubectl "${kctx[@]}" -n ingress-nginx get svc ingress-nginx-controller \
            -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null || true)
        [[ -n "$elb" ]] && break
        echo "Waiting for ingress load balancer hostname... ($i)"
        sleep 5
    done
    if [[ -z "$elb" ]]; then
        echo "Skipping DNS: ingress load balancer hostname not available."
        return
    fi

    hosts=$(kubectl "${kctx[@]}" -n "$ns" get ingress \
        -o jsonpath='{range .items[*]}{range .spec.rules[*]}{.host}{"\n"}{end}{end}' 2>/dev/null \
        | sort -u | grep -v '^$' || true)
    if [[ -z "$hosts" ]]; then
        echo "Skipping DNS: no ingress hosts found in namespace $ns."
        return
    fi

    # hosted zone: explicit override, else best-effort match on the hosts' apex domain
    zone_id=$ROUTE53_ZONE_ID
    if [[ -z "$zone_id" ]]; then
        local domain
        domain=$(echo "$hosts" | head -1 | rev | cut -d. -f1-2 | rev)
        zone_id=$(aws route53 list-hosted-zones \
            --query "HostedZones[?Name=='${domain}.'].Id | [0]" --output text 2>/dev/null || true)
    fi
    zone_id=${zone_id#/hostedzone/}
    if [[ -z "$zone_id" || "$zone_id" == "None" ]]; then
        echo "Skipping DNS: no Route 53 hosted zone for these hosts (set ROUTE53_ZONE_ID to force)."
        return
    fi

    # the ELB's canonical hosted zone id (required for alias records); try v2 then classic
    elb_zone=$(aws elbv2 describe-load-balancers \
        --query "LoadBalancers[?DNSName=='${elb}'].CanonicalHostedZoneId | [0]" --output text 2>/dev/null || true)
    if [[ -z "$elb_zone" || "$elb_zone" == "None" ]]; then
        elb_zone=$(aws elb describe-load-balancers \
            --query "LoadBalancerDescriptions[?DNSName=='${elb}'].CanonicalHostedZoneNameID | [0]" --output text 2>/dev/null || true)
    fi
    if [[ -z "$elb_zone" || "$elb_zone" == "None" ]]; then
        echo "Skipping DNS: could not resolve the load balancer's canonical hosted zone id."
        return
    fi

    echo "Registering DNS in Route 53 zone $zone_id (target: $elb)..."
    for h in $hosts; do
        echo "  UPSERT alias A  $h  ->  $elb"
        aws route53 change-resource-record-sets --hosted-zone-id "$zone_id" --change-batch "{
            \"Comment\": \"rtgs-signing helm-up-eks\",
            \"Changes\": [{
                \"Action\": \"UPSERT\",
                \"ResourceRecordSet\": {
                    \"Name\": \"${h}.\",
                    \"Type\": \"A\",
                    \"AliasTarget\": {
                        \"HostedZoneId\": \"${elb_zone}\",
                        \"DNSName\": \"${elb}.\",
                        \"EvaluateTargetHealth\": false
                    }
                }
            }]
        }" >/dev/null
    done
    echo "DNS registered. cert-manager will issue the TLS certs once records propagate."
    echo
}

# ------
# LOGIC:
# ------
install_nginx
install_metrics_server
install_cnpg
install_cert_manager
ensure_cluster_issuer
helm_install

register_dns

echo "Done. Ingress address:"
kubectl "${kctx[@]}" -n ingress-nginx get svc ingress-nginx-controller -o wide 2>/dev/null || true
