#!/usr/bin/env bash
# =============================================================================
#  UiPath Automation Suite on OpenShift
#  FULL REINSTALL: 2024.10.x  →  2024.10.7  (latest patch as of 2026-03)
#
#  Author  : Gustavo
#  Version : 2.0
#  Date    : 2026-03-21
#
#  What this script does (in order):
#    0.  Validate required tools on the bastion host
#    1.  Auto-discover ALL credentials from the live OpenShift environment
#        (OBC secret, SQL secret, UiPath admin password, OBS host/port)
#    2.  Show a pre-flight summary and ask for confirmation
#    3.  Remove the current UiPath installation and its namespace
#        (including the orphaned istio-plugin pod visible in istio-system)
#    4.  Download uipathctl 2024.10.7 + versions.json + image/chart lists
#    5.  Recreate namespace, ServiceAccount and all RBAC
#    6.  Generate a kubeconfig for the uipathadmin SA (1-year token)
#    7.  Generate input.json (HA profile, online install, Ceph/S3 storage)
#    8.  Run prereq create  (create SQL databases + verify buckets)
#    9.  Run prereq run     (cluster validation)
#   10.  Run manifest apply (actual install)
#   11.  Post-install health check
#
#  REQUIREMENTS BEFORE RUNNING:
#    - Logged in as cluster-admin on the bastion:  oc login ...
#    - The following must already exist (managed by the platform team):
#        * OpenShift Service Mesh (istio-system namespace + control plane)
#        * Redis instance (via OperatorHub or standalone)
#        * ObjectBucketClaim  bucket-uipath-lab  in  app-uipath-lab
#        * SQL Server instance reachable from the cluster
#    - Tools required: oc, helm, wget, tar, jq, base64, curl
#
#  HOW TO RUN:
#    chmod +x uipath-reinstall-2024.10.7.sh
#    ./uipath-reinstall-2024.10.7.sh
#
#  OPTIONAL OVERRIDES (env vars you can set before running):
#    UIPATH_SQL_PASSWORD    - if SQL password is not stored in a cluster secret
#    UIPATH_ADMIN_PASSWORD  - if UiPath admin password must be set manually
#    UIPATH_REGISTRY_USER   - OCI registry username (leave empty for no auth)
#    UIPATH_REGISTRY_PASS   - OCI registry password
# =============================================================================

set -euo pipefail

# ─── logging setup ──────────────────────────────────────────────────────────
# All stdout+stderr is captured to a timestamped log file via tee.
# The log is always written, even if the script succeeds — useful for auditing.
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_DIR="${HOME}/uipath-logs"
mkdir -p "${LOG_DIR}"
LOG_FILE="${LOG_DIR}/uipath-install_${TIMESTAMP}.log"

# Redirect all output through tee (preserves terminal colours + writes plain log)
exec > >(tee -a "${LOG_FILE}") 2>&1

# ─── colour helpers ───────────────────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'

info()    { echo -e "${CYAN}[INFO]${NC}    $(date +%H:%M:%S)  $*"; }
ok()      { echo -e "${GREEN}[OK]${NC}      $(date +%H:%M:%S)  $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}    $(date +%H:%M:%S)  $*"; }
die()     { echo -e "${RED}[FATAL]${NC}   $(date +%H:%M:%S)  $*" >&2; exit 1; }
section() { echo -e "\n${BOLD}================================================${NC}"
            echo -e "${BOLD}  $*${NC}"
            echo -e "${BOLD}================================================${NC}"; }

confirm() {
  read -rp "$(echo -e "${YELLOW}>>> $* [yes/N]: ${NC}")" ans
  [[ "$ans" == "yes" ]] || { warn "Aborted by user."; exit 0; }
}

# ─── diagnostic collection on failure ───────────────────────────────────────
# Called automatically by the ERR trap. Gathers cluster state into a tarball
# so the issue can be investigated without live access.
collect_diagnostics() {
  local exit_code=$?
  local failed_line=${1:-unknown}
  local failed_cmd=${2:-unknown}
  local diag_dir="${LOG_DIR}/diagnostics_${TIMESTAMP}"

  echo -e "\n${RED}[FATAL]${NC}   Script failed at line ${failed_line} (exit code ${exit_code})"
  echo -e "${RED}[FATAL]${NC}   Command: ${failed_cmd}"
  echo -e "${YELLOW}[DIAG]${NC}    Collecting diagnostics into ${diag_dir}/ ..."

  mkdir -p "${diag_dir}"

  # Snapshot of what was running (or not) at the time of failure
  {
    echo "=== Failure context ==="
    echo "Date:     $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo "Line:     ${failed_line}"
    echo "Exit:     ${exit_code}"
    echo "Command:  ${failed_cmd}"
    echo "User:     $(whoami)"
    echo "Hostname: $(hostname)"
    echo ""
    echo "=== Environment (non-secret) ==="
    echo "NEW_VERSION=${NEW_VERSION:-unset}"
    echo "NAMESPACE=${NAMESPACE:-unset}"
    echo "FQDN=${FQDN:-unset}"
    echo "WORKDIR=${WORKDIR:-unset}"
    echo "KUBECONFIG_PATH=${KUBECONFIG_PATH:-unset}"
  } > "${diag_dir}/failure_context.txt" 2>&1

  # Cluster state (best-effort — commands may fail if oc is not logged in)
  if command -v oc &>/dev/null && oc whoami &>/dev/null 2>&1; then
    oc get pods -n "${NAMESPACE:-app-uipath-lab}" -o wide \
      > "${diag_dir}/pods.txt" 2>&1 || true

    oc get events -n "${NAMESPACE:-app-uipath-lab}" --sort-by='.lastTimestamp' \
      > "${diag_dir}/events.txt" 2>&1 || true

    oc get pods -n "${NAMESPACE:-app-uipath-lab}" -o jsonpath='{range .items[?(@.status.phase!="Running")]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{.status.conditions[*].message}{"\n"}{end}' \
      > "${diag_dir}/unhealthy_pods.txt" 2>&1 || true

    # Logs from pods in CrashLoopBackOff or Error state
    local crashing
    crashing=$(oc get pods -n "${NAMESPACE:-app-uipath-lab}" --no-headers 2>/dev/null \
      | grep -iE "error|crash|init" | awk '{print $1}' | head -10 || true)
    if [[ -n "${crashing}" ]]; then
      mkdir -p "${diag_dir}/pod_logs"
      for pod in ${crashing}; do
        oc logs "${pod}" -n "${NAMESPACE:-app-uipath-lab}" --tail=200 \
          > "${diag_dir}/pod_logs/${pod}.log" 2>&1 || true
        oc logs "${pod}" -n "${NAMESPACE:-app-uipath-lab}" --previous --tail=100 \
          > "${diag_dir}/pod_logs/${pod}_previous.log" 2>&1 || true
      done
    fi

    oc get nodes -o wide > "${diag_dir}/nodes.txt" 2>&1 || true

    oc get pvc -n "${NAMESPACE:-app-uipath-lab}" \
      > "${diag_dir}/pvcs.txt" 2>&1 || true

    oc get objectbucketclaim -n "${NAMESPACE:-app-uipath-lab}" -o yaml \
      > "${diag_dir}/obc.yaml" 2>&1 || true
  else
    echo "oc not available or not logged in — skipping cluster diagnostics" \
      > "${diag_dir}/oc_unavailable.txt"
  fi

  # Helm state
  if command -v helm &>/dev/null; then
    helm list -n "${NAMESPACE:-app-uipath-lab}" -a \
      > "${diag_dir}/helm_releases.txt" 2>&1 || true
  fi

  # Copy input.json (if generated) — strip passwords
  if [[ -f "${WORKDIR:-/dev/null}/input.json" ]]; then
    sed -E 's/("password":\s*")[^"]+/\1***REDACTED***/g' \
      "${WORKDIR}/input.json" > "${diag_dir}/input_redacted.json" 2>&1 || true
  fi

  # Copy the main log file
  cp "${LOG_FILE}" "${diag_dir}/install.log" 2>/dev/null || true

  # Bundle everything into a tarball
  local tarball="${LOG_DIR}/uipath-diag_${TIMESTAMP}.tar.gz"
  tar -czf "${tarball}" -C "${LOG_DIR}" "diagnostics_${TIMESTAMP}" 2>/dev/null || true
  rm -rf "${diag_dir}"

  echo -e "${YELLOW}[DIAG]${NC}    Diagnostic bundle: ${tarball}"
  echo -e "${YELLOW}[DIAG]${NC}    Full log: ${LOG_FILE}"
  echo -e "${YELLOW}[DIAG]${NC}    Share these files when requesting support."
}

# ─── error trap ─────────────────────────────────────────────────────────────
trap 'collect_diagnostics "${LINENO}" "${BASH_COMMAND}"' ERR

# ─── static configuration (non-secret) ───────────────────────────────────────
readonly NEW_VERSION="2024.10.7"
readonly NAMESPACE="app-uipath-lab"
readonly ISTIO_NS="istio-system"
readonly WORKDIR="${HOME}/uipath-${NEW_VERSION}"
readonly FQDN="uipathlb.in.ep.europa.eu"
readonly SQL_HOST="10.27.72.191"
readonly SQL_HOST_FQDN="eicilwl8076.ep.parl.union.eu"
readonly SQL_PORT="1433"
readonly SQL_USER="eici-app-sql-uipath-lb"
readonly SQL_PASSWORD_DEFAULT="KH5ohSTkAhOMWFIQ"
readonly ADMIN_USER="epadmin"
readonly ADMIN_PASSWORD_DEFAULT="gDMhUWaaMTZW3d40tiknJwqD"
readonly OBC_NAME="bucket-uipath-lab"
readonly OBC_NAMESPACE="${NAMESPACE}"
readonly KUBECONFIG_PATH="${WORKDIR}/uipathadminkubeconfig"

# ─── proxy configuration ──────────────────────────────────────────────────────
# Required to reach download.uipath.com from the EP network.
# The no_proxy list ensures internal cluster traffic bypasses the proxy:
#   - localhost          : loopback
#   - .ep.parl.union.eu : all internal EP hostnames (SQL, OBS, API server, etc.)
#
# These variables are exported so they are inherited by wget, curl, and any
# child process spawned by this script (including uipathctl for online installs).

export HTTPS_PROXY="http://itecluxafpvip01.ep.parl.union.eu:8082/"
export HTTP_PROXY="http://itecluxafpvip01.ep.parl.union.eu:8082/"
export https_proxy="${HTTPS_PROXY}"
export http_proxy="${HTTP_PROXY}"
# OBS_HOST is discovered in Section 1b; NO_PROXY is re-exported there to include it.
export NO_PROXY="localhost,127.0.0.1,.ep.parl.union.eu,${SQL_HOST},10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
export no_proxy="${NO_PROXY}"

# wget proxy settings (wget also reads the env vars above, but we set wgetrc
# entries explicitly to handle edge cases with older wget versions)
WGET_OPTS="--no-check-certificate -e use_proxy=yes -e https_proxy=${HTTPS_PROXY}"

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 0 - TOOL CHECK
# ─────────────────────────────────────────────────────────────────────────────
section "0. Checking required tools on bastion host"

for cmd in oc helm wget tar jq base64 curl; do
  if command -v "$cmd" &>/dev/null; then
    ok "$cmd  ->  $(command -v "$cmd")"
  else
    die "'$cmd' not found. Please install it before continuing."
  fi
done

oc whoami &>/dev/null || die "Not logged in to OpenShift. Run: oc login --server=<API> --token=<TOKEN>"
OC_USER=$(oc whoami)
OC_SERVER=$(oc config view -o jsonpath="{.clusters[0].cluster.server}" 2>/dev/null || echo "unknown")
ok "Logged in as '${OC_USER}'  ->  ${OC_SERVER}"

# Confirm proxy vars are active in this shell session
info "Proxy settings active for this session:"
info "  HTTPS_PROXY = ${HTTPS_PROXY}"
info "  NO_PROXY    = ${NO_PROXY}"

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 1 - AUTO-DISCOVER CREDENTIALS FROM THE CLUSTER
# ─────────────────────────────────────────────────────────────────────────────
section "1. Auto-discovering credentials from the cluster"

# ─── 1a. Object Storage credentials from the Ceph OBC secret ─────────────────
#
# When a Ceph ObjectBucketClaim is created, the Rook provisioner automatically
# creates a Secret (and a ConfigMap) with the same name as the OBC, in the same
# namespace. The Secret contains:
#   - AWS_ACCESS_KEY_ID   (base64-encoded)
#   - AWS_SECRET_ACCESS_KEY (base64-encoded)
# The ConfigMap contains:
#   - BUCKET_HOST  (the Ceph RGW service DNS name)
#   - BUCKET_PORT
#   - BUCKET_NAME
#
# Reference: https://rook.io/docs/rook/v1.10/Storage-Configuration/Object-Storage-RGW/ceph-object-bucket-claim/

info "Looking for OBC secret '${OBC_NAME}' in namespace '${OBC_NAMESPACE}'..."

if oc get secret "${OBC_NAME}" -n "${OBC_NAMESPACE}" &>/dev/null 2>&1; then
  OBS_ACCESS_KEY=$(oc get secret "${OBC_NAME}" -n "${OBC_NAMESPACE}" \
    -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d)
  OBS_SECRET_KEY=$(oc get secret "${OBC_NAME}" -n "${OBC_NAMESPACE}" \
    -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
  ok "S3 access key found in secret '${OBC_NAME}' (namespace: ${OBC_NAMESPACE})."
else
  # Namespace may have already been deleted. Search cluster-wide.
  info "Secret not in '${OBC_NAMESPACE}'. Searching all namespaces..."
  OBC_NS_FOUND=$(oc get secrets -A --no-headers 2>/dev/null \
    | grep "^[^ ]* *${OBC_NAME} " | head -1 | awk '{print $1}' || true)

  if [[ -n "${OBC_NS_FOUND}" ]]; then
    OBS_ACCESS_KEY=$(oc get secret "${OBC_NAME}" -n "${OBC_NS_FOUND}" \
      -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d)
    OBS_SECRET_KEY=$(oc get secret "${OBC_NAME}" -n "${OBC_NS_FOUND}" \
      -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
    warn "OBC secret found in namespace '${OBC_NS_FOUND}' (not '${OBC_NAMESPACE}')."
    ok "S3 credentials retrieved."
  else
    warn "OBC secret '${OBC_NAME}' not found anywhere."
    warn "The ObjectBucketClaim will be recreated in Section 3b."
    warn "S3 credentials will be re-read after the OBC is Bound."
    OBS_ACCESS_KEY="PENDING_OBC_CREATION"
    OBS_SECRET_KEY="PENDING_OBC_CREATION"
  fi
fi

# ─── 1b. OBS endpoint from the OBC ConfigMap ─────────────────────────────────
info "Looking for OBC ConfigMap '${OBC_NAME}'..."

if oc get configmap "${OBC_NAME}" -n "${OBC_NAMESPACE}" &>/dev/null 2>&1; then
  OBS_HOST=$(oc get configmap "${OBC_NAME}" -n "${OBC_NAMESPACE}" \
    -o jsonpath='{.data.BUCKET_HOST}' 2>/dev/null || echo "")
  OBS_PORT=$(oc get configmap "${OBC_NAME}" -n "${OBC_NAMESPACE}" \
    -o jsonpath='{.data.BUCKET_PORT}' 2>/dev/null || echo "443")
  if [[ -n "${OBS_HOST}" ]]; then
    ok "OBS endpoint from ConfigMap: ${OBS_HOST}:${OBS_PORT}"
  else
    warn "BUCKET_HOST not set in ConfigMap. Defaulting to known Ceph RGW SVC."
    OBS_HOST="rook-ceph-rgw-ceph-objectstore.adm-ceph-storage.svc"
    OBS_PORT="443"
  fi
else
  warn "OBC ConfigMap '${OBC_NAME}' not found. Using known Ceph RGW SVC address."
  OBS_HOST="rook-ceph-rgw-ceph-objectstore.adm-ceph-storage.svc"
  OBS_PORT="443"
fi

# Re-export NO_PROXY now that OBS_HOST is known
export NO_PROXY="localhost,127.0.0.1,.ep.parl.union.eu,${SQL_HOST},${OBS_HOST},10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
export no_proxy="${NO_PROXY}"

# ─── 1c. SQL Server password ──────────────────────────────────────────────────
#
# Discovery order:
#   1. Env var UIPATH_SQL_PASSWORD (set externally before running the script)
#   2. Secret 'uipath-sql-credentials' in the UiPath namespace
#   3. Any secret in the namespace whose name contains "sql" or "db"
#   4. Interactive prompt (fallback)

if [[ -n "${UIPATH_SQL_PASSWORD:-}" ]]; then
  SQL_PASSWORD="${UIPATH_SQL_PASSWORD}"
  ok "SQL password taken from env var UIPATH_SQL_PASSWORD."

elif oc get secret uipath-sql-credentials -n "${NAMESPACE}" &>/dev/null 2>&1; then
  SQL_PASSWORD=$(oc get secret uipath-sql-credentials -n "${NAMESPACE}" \
    -o jsonpath='{.data.password}' | base64 -d)
  ok "SQL password retrieved from secret 'uipath-sql-credentials'."

elif SQL_SECRET=$(oc get secret -n "${NAMESPACE}" --no-headers 2>/dev/null \
     | grep -i "sql\|db\|database" | head -1 | awk '{print $1}'); \
     [[ -n "${SQL_SECRET:-}" ]]; then
  info "Trying potential SQL secret: ${SQL_SECRET}..."
  SQL_PASSWORD=$(oc get secret "${SQL_SECRET}" -n "${NAMESPACE}" \
    -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || echo "")
  if [[ -n "${SQL_PASSWORD}" ]]; then
    ok "SQL password retrieved from secret '${SQL_SECRET}'."
  else
    SQL_PASSWORD=""
  fi
else
  SQL_PASSWORD=""
fi

if [[ -z "${SQL_PASSWORD:-}" ]]; then
  SQL_PASSWORD="${SQL_PASSWORD_DEFAULT}"
  ok "SQL password set to built-in default."
  [[ -n "${SQL_PASSWORD}" ]] || die "SQL password cannot be empty."
fi

# ─── 1d. UiPath admin password ────────────────────────────────────────────────
#
# Discovery order:
#   1. Env var UIPATH_ADMIN_PASSWORD
#   2. Secret 'platform-service-secrets' (left by previous install)
#   3. Secret 'uipath-admin-credentials'
#   4. Built-in default (ADMIN_PASSWORD_DEFAULT)

if [[ -n "${UIPATH_ADMIN_PASSWORD:-}" ]]; then
  ADMIN_PASSWORD="${UIPATH_ADMIN_PASSWORD}"
  ok "Admin password taken from env var UIPATH_ADMIN_PASSWORD."

elif oc get secret platform-service-secrets -n "${NAMESPACE}" &>/dev/null 2>&1; then
  ADMIN_PASSWORD=$(oc get secret platform-service-secrets -n "${NAMESPACE}" \
    -o jsonpath='{.data.adminPassword}' 2>/dev/null | base64 -d 2>/dev/null || echo "")
  [[ -n "${ADMIN_PASSWORD}" ]] \
    && ok "Admin password retrieved from 'platform-service-secrets'." \
    || ADMIN_PASSWORD=""

elif oc get secret uipath-admin-credentials -n "${NAMESPACE}" &>/dev/null 2>&1; then
  ADMIN_PASSWORD=$(oc get secret uipath-admin-credentials -n "${NAMESPACE}" \
    -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || echo "")
  [[ -n "${ADMIN_PASSWORD}" ]] \
    && ok "Admin password retrieved from 'uipath-admin-credentials'." \
    || ADMIN_PASSWORD=""
else
  ADMIN_PASSWORD=""
fi

if [[ -z "${ADMIN_PASSWORD:-}" ]]; then
  ADMIN_PASSWORD="${ADMIN_PASSWORD_DEFAULT}"
  ok "Admin password set to built-in default."
  warn "CHANGE THIS PASSWORD after first login at https://${FQDN}"
fi

# ─── 1e. OCI registry credentials (optional) ─────────────────────────────────
REGISTRY_USER="${UIPATH_REGISTRY_USER:-}"
REGISTRY_PASS="${UIPATH_REGISTRY_PASS:-}"
if [[ -z "${REGISTRY_USER}" ]]; then
  if oc get secret uipath-registry -n "${NAMESPACE}" &>/dev/null 2>&1; then
    REGISTRY_USER=$(oc get secret uipath-registry -n "${NAMESPACE}" \
      -o jsonpath='{.data.username}' 2>/dev/null | base64 -d || echo "")
    REGISTRY_PASS=$(oc get secret uipath-registry -n "${NAMESPACE}" \
      -o jsonpath='{.data.password}' 2>/dev/null | base64 -d || echo "")
    [[ -n "${REGISTRY_USER}" ]] \
      && ok "OCI registry credentials retrieved from cluster." \
      || info "No OCI registry credentials found - unauthenticated registry will be used."
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 2 - PRE-FLIGHT SUMMARY
# ─────────────────────────────────────────────────────────────────────────────
section "2. Pre-flight summary"

echo -e "
  Target version   : ${BOLD}${NEW_VERSION}${NC}
  Namespace        : ${BOLD}${NAMESPACE}${NC}
  Istio namespace  : ${BOLD}${ISTIO_NS}${NC}
  FQDN             : ${BOLD}${FQDN}${NC}
  Work directory   : ${BOLD}${WORKDIR}${NC}

  HTTPS Proxy      : ${BOLD}${HTTPS_PROXY}${NC}
  No Proxy         : ${BOLD}${NO_PROXY}${NC}

  SQL Server       : ${BOLD}${SQL_HOST}:${SQL_PORT}${NC}
  SQL User         : ${BOLD}${SQL_USER}${NC}
  SQL Password     : ${BOLD}$([ -n "${SQL_PASSWORD}" ] && echo '***SET***' || echo 'MISSING')${NC}

  S3 / OBS Host    : ${BOLD}${OBS_HOST}:${OBS_PORT}${NC}
  S3 Access Key    : ${BOLD}$(echo "${OBS_ACCESS_KEY}" | cut -c1-6)...${NC}
  S3 Secret Key    : ${BOLD}$(echo "${OBS_SECRET_KEY}" | cut -c1-6)...${NC}

  UiPath Admin     : ${BOLD}${ADMIN_USER}${NC}
  Admin Password   : ${BOLD}$([ -n "${ADMIN_PASSWORD}" ] && echo '***SET***' || echo 'MISSING')${NC}

  OCI Registry     : ${BOLD}$([ -n "${REGISTRY_USER}" ] && echo "${REGISTRY_USER}" || echo '(unauthenticated)')${NC}
"

# Verify proxy can reach download.uipath.com before doing anything destructive
info "Verifying proxy connectivity to download.uipath.com..."
if curl -s --max-time 10 \
     -x "${HTTPS_PROXY}" \
     -o /dev/null -w "%{http_code}" \
     "https://download.uipath.com/uipathctl/${NEW_VERSION}/uipathctl-${NEW_VERSION}-linux-amd64.tar.gz" \
   | grep -qE "^(200|206|302)"; then
  ok "Proxy connectivity to download.uipath.com verified."
else
  HTTP_CODE=$(curl -s --max-time 10 \
    -x "${HTTPS_PROXY}" \
    -o /dev/null -w "%{http_code}" \
    "https://download.uipath.com" 2>/dev/null || echo "000")
  die "Cannot reach download.uipath.com through proxy (HTTP ${HTTP_CODE}).
  Proxy : ${HTTPS_PROXY}
  Test  : curl -x \"${HTTPS_PROXY}\" -I https://download.uipath.com"
fi

confirm "Review the values above. Type 'yes' to proceed with FULL REINSTALL"

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3 - REMOVE CURRENT INSTALLATION
# ─────────────────────────────────────────────────────────────────────────────
section "3. Removing current UiPath installation"

# ─── 3a. Remove the orphaned UiPath Istio plugin pod in istio-system ─────────
#
# What you are seeing in the cluster:
#   istio-system  app-uipath-lab-istio-plugin-http-bbcd867c6-6ms6j
#
# This is a UiPath WASM/HTTP plugin Deployment that was deployed into
# istio-system as part of the previous install. It must be removed before
# the namespace is deleted (since it is NOT in app-uipath-lab).

info "Removing UiPath Istio plugin resources from '${ISTIO_NS}'..."

for rtype in deployment replicaset pod; do
  OBJS=$(oc get "${rtype}" -n "${ISTIO_NS}" --no-headers 2>/dev/null \
    | grep -iE "uipath|${NAMESPACE}" | awk '{print $1}' || true)
  if [[ -n "${OBJS}" ]]; then
    echo "${OBJS}" | xargs -r oc delete "${rtype}" -n "${ISTIO_NS}" \
      --ignore-not-found=true --grace-period=0 --force 2>/dev/null || true
    ok "Deleted ${rtype}(s) in ${ISTIO_NS}: ${OBJS}"
  fi
done

# Also clean up UiPath-specific Istio CRD objects in istio-system
for crd_type in wasmplugins.extensions.istio.io \
                envoyfilters.networking.istio.io \
                virtualservices.networking.istio.io \
                peerauthentications.security.istio.io; do
  if oc get crd "${crd_type}" &>/dev/null 2>&1; then
    OBJS=$(oc get "${crd_type}" -n "${ISTIO_NS}" --no-headers 2>/dev/null \
      | grep -iE "uipath" | awk '{print $1}' || true)
    if [[ -n "${OBJS}" ]]; then
      echo "${OBJS}" | xargs -r oc delete "${crd_type}" -n "${ISTIO_NS}" \
        --ignore-not-found=true 2>/dev/null || true
      ok "Deleted ${crd_type} objects in ${ISTIO_NS}."
    fi
  fi
done

# ─── 3b. Protect the OBC (bucket data) before namespace deletion ──────────────
#
# The ObjectBucketClaim lives in the namespace. When the namespace is deleted,
# Kubernetes may trigger the OBC deletion which in turn triggers the bucket
# deletion (if reclaimPolicy=Delete). To be safe, we annotate the cluster-scoped
# ObjectBucket to prevent that and, if possible, set the reclaimPolicy to Retain.

info "Protecting OBC '${OBC_NAME}' from accidental bucket deletion..."
if oc get objectbucketclaim "${OBC_NAME}" -n "${OBC_NAMESPACE}" &>/dev/null 2>&1; then
  OB_NAME=$(oc get objectbucketclaim "${OBC_NAME}" -n "${OBC_NAMESPACE}" \
    -o jsonpath='{.spec.objectBucketName}' 2>/dev/null || echo "")
  if [[ -n "${OB_NAME}" ]]; then
    # Set reclaimPolicy to Retain on the backing ObjectBucket
    oc patch objectbucket "${OB_NAME}" \
      --type=merge -p '{"spec":{"reclaimPolicy":"Retain"}}' 2>/dev/null \
      && ok "ObjectBucket '${OB_NAME}' reclaimPolicy set to Retain." \
      || warn "Could not patch ObjectBucket reclaimPolicy (may already be Retain)."
  fi
fi

# ─── 3c. Remove ArgoCD Applications ──────────────────────────────────────────
info "Removing ArgoCD Applications in '${NAMESPACE}'..."
if oc api-resources --api-group=argoproj.io 2>/dev/null | grep -q "applications"; then
  APPS=$(oc get applications.argoproj.io -n "${NAMESPACE}" \
    --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null || true)
  if [[ -n "${APPS}" ]]; then
    echo "${APPS}" | xargs -r oc delete applications.argoproj.io \
      -n "${NAMESPACE}" --ignore-not-found=true
    ok "ArgoCD Applications removed."
  else
    info "No ArgoCD Applications found."
  fi
else
  info "argoproj.io CRDs not present (ArgoCD excluded from this install - OK)."
fi

# ─── 3d. Remove Helm releases ────────────────────────────────────────────────
info "Checking Helm releases in '${NAMESPACE}'..."
HELM_RELEASES=$(helm list -n "${NAMESPACE}" --short 2>/dev/null || true)
if [[ -n "${HELM_RELEASES}" ]]; then
  info "Uninstalling: ${HELM_RELEASES}"
  for rel in ${HELM_RELEASES}; do
    helm uninstall "${rel}" -n "${NAMESPACE}" 2>/dev/null || true
  done
  ok "Helm releases removed."
else
  info "No Helm releases found."
fi

# ─── 3e. Clean UiPath resources from the namespace (without deleting it) ─────
#
# Deleting the namespace on OpenShift with Istio/Service Mesh often causes
# stuck Terminating state due to finalizers that require cluster-admin to fix.
# Instead, we clean all UiPath-related resources inside the namespace and
# reinstall on top. This preserves the namespace, OBC, and avoids the issue.

if oc get namespace "${NAMESPACE}" &>/dev/null; then
  info "Cleaning UiPath resources inside '${NAMESPACE}' (namespace preserved)..."

  # Delete all UiPath deployments, statefulsets, daemonsets, jobs, replicasets, pods
  for rtype in deployments statefulsets daemonsets jobs replicasets pods; do
    OBJS=$(oc get "${rtype}" -n "${NAMESPACE}" --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null || true)
    if [[ -n "${OBJS}" ]]; then
      echo "${OBJS}" | xargs -r oc delete "${rtype}" -n "${NAMESPACE}" \
        --ignore-not-found=true --grace-period=5 2>/dev/null || true
      ok "Deleted ${rtype} in ${NAMESPACE}."
    fi
  done

  # Delete services, configmaps (except OBC), secrets (except OBC and SA tokens)
  for rtype in services configmaps; do
    OBJS=$(oc get "${rtype}" -n "${NAMESPACE}" --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null \
      | grep -vE "^${OBC_NAME}$|^kube-root-ca" || true)
    if [[ -n "${OBJS}" ]]; then
      echo "${OBJS}" | xargs -r oc delete "${rtype}" -n "${NAMESPACE}" \
        --ignore-not-found=true 2>/dev/null || true
      ok "Deleted ${rtype} in ${NAMESPACE} (OBC preserved)."
    fi
  done

  # Delete secrets except OBC secret and default SA tokens
  SECRETS=$(oc get secrets -n "${NAMESPACE}" --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null \
    | grep -vE "^${OBC_NAME}$|^default-|^builder-|^deployer-|^uipathadmin-" || true)
  if [[ -n "${SECRETS}" ]]; then
    echo "${SECRETS}" | xargs -r oc delete secret -n "${NAMESPACE}" \
      --ignore-not-found=true 2>/dev/null || true
    ok "Deleted secrets in ${NAMESPACE} (OBC + SA tokens preserved)."
  fi

  # Delete PVCs (UiPath will recreate them)
  oc delete pvc --all -n "${NAMESPACE}" --ignore-not-found=true 2>/dev/null || true

  # Delete Istio CRD objects in the namespace
  for crd_type in virtualservices destinationrules gateways serviceentries \
                  peerauthentications authorizationpolicies requestauthentications; do
    oc delete "${crd_type}.networking.istio.io" --all -n "${NAMESPACE}" \
      --ignore-not-found=true 2>/dev/null || true
    oc delete "${crd_type}.security.istio.io" --all -n "${NAMESPACE}" \
      --ignore-not-found=true 2>/dev/null || true
  done

  ok "Namespace '${NAMESPACE}' cleaned. OBC and namespace preserved."
else
  info "Namespace '${NAMESPACE}' does not exist - will be created."
fi

# ─── 3f. Remove cluster-scoped RBAC ──────────────────────────────────────────
info "Cleaning up cluster-scoped RBAC from previous installation..."
for cr in uipathadmin-limited uipathadmin-namespace-reader namespace-creator \
          list-nodes-and-crd-clusterrole namespace-reader-clusterrole \
          istio-crd-admin-role; do
  oc delete clusterrole "${cr}" --ignore-not-found=true 2>/dev/null || true
done
for crb in uipathadmin-limited-binding uipathadmin-namespace-reader-binding \
           uipathadmin-namespace-creator list-nodes-and-crd-rolebinding \
           istio-crd-admin-rolebinding; do
  oc delete clusterrolebinding "${crb}" --ignore-not-found=true 2>/dev/null || true
done
ok "Cluster RBAC cleaned up."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3b - RECREATE OBC IF DELETED WITH THE NAMESPACE
# ─────────────────────────────────────────────────────────────────────────────
section "3b. Verifying / recreating ObjectBucketClaim"

oc get namespace "${NAMESPACE}" &>/dev/null || oc new-project "${NAMESPACE}"
oc project "${NAMESPACE}"

# If S3 credentials were already discovered in Section 1, skip OBC recreation entirely.
# The OBC may live in a different namespace or the user may lack objectbucketclaim permissions.
if [[ "${OBS_ACCESS_KEY}" != "PENDING_OBC_CREATION" ]]; then
  ok "S3 credentials already available from Section 1. Skipping OBC verification."
  ok "  Endpoint: ${OBS_HOST}:${OBS_PORT}"
  ok "  Access key: $(echo "${OBS_ACCESS_KEY}" | cut -c1-6)..."

elif oc get objectbucketclaim "${OBC_NAME}" -n "${NAMESPACE}" &>/dev/null 2>&1; then
  ok "OBC '${OBC_NAME}' already exists in '${NAMESPACE}'."
  # Re-read credentials
  OBS_ACCESS_KEY=$(oc get secret "${OBC_NAME}" -n "${NAMESPACE}" \
    -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d)
  OBS_SECRET_KEY=$(oc get secret "${OBC_NAME}" -n "${NAMESPACE}" \
    -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
  OBS_HOST=$(oc get configmap "${OBC_NAME}" -n "${NAMESPACE}" \
    -o jsonpath='{.data.BUCKET_HOST}' 2>/dev/null \
    || echo "rook-ceph-rgw-ceph-objectstore.adm-ceph-storage.svc")
  OBS_PORT=$(oc get configmap "${OBC_NAME}" -n "${NAMESPACE}" \
    -o jsonpath='{.data.BUCKET_PORT}' 2>/dev/null || echo "443")
  ok "S3 credentials reloaded. Endpoint: ${OBS_HOST}:${OBS_PORT}"

else
  warn "OBC '${OBC_NAME}' not found and S3 credentials not available."
  warn "Attempting to create OBC (requires objectbucketclaim permissions)..."
  if cat <<YAML | oc apply -f - 2>/dev/null
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: ${OBC_NAME}
  namespace: ${NAMESPACE}
spec:
  bucketName: ${OBC_NAME}
  storageClassName: ceph-bucket
YAML
  then
    info "Waiting for OBC to reach Bound state (max 3 min)..."
    DEADLINE=$((SECONDS + 180))
    while true; do
      PHASE=$(oc get objectbucketclaim "${OBC_NAME}" -n "${NAMESPACE}" \
        -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
      [[ "${PHASE}" == "Bound" ]] && break
      [[ $SECONDS -lt $DEADLINE ]] \
        || die "OBC '${OBC_NAME}' did not reach Bound state. Check the Ceph provisioner."
      info "  ... OBC phase: '${PHASE:-Pending}', retrying in 10s ..."
      sleep 10
    done
    ok "OBC '${OBC_NAME}' is Bound."

    OBS_ACCESS_KEY=$(oc get secret "${OBC_NAME}" -n "${NAMESPACE}" \
      -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d)
    OBS_SECRET_KEY=$(oc get secret "${OBC_NAME}" -n "${NAMESPACE}" \
      -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
    OBS_HOST=$(oc get configmap "${OBC_NAME}" -n "${NAMESPACE}" \
      -o jsonpath='{.data.BUCKET_HOST}' 2>/dev/null \
      || echo "rook-ceph-rgw-ceph-objectstore.adm-ceph-storage.svc")
    OBS_PORT=$(oc get configmap "${OBC_NAME}" -n "${NAMESPACE}" \
      -o jsonpath='{.data.BUCKET_PORT}' 2>/dev/null || echo "443")
    ok "New S3 credentials loaded. Endpoint: ${OBS_HOST}:${OBS_PORT}"
  else
    die "Cannot create OBC — insufficient permissions.
  Ask a cluster-admin to create it:
    oc apply -f - <<< '{\"apiVersion\":\"objectbucket.io/v1alpha1\",\"kind\":\"ObjectBucketClaim\",\"metadata\":{\"name\":\"${OBC_NAME}\",\"namespace\":\"${NAMESPACE}\"},\"spec\":{\"bucketName\":\"${OBC_NAME}\",\"storageClassName\":\"ceph-bucket\"}}'
  Then re-run this script."
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 4 - DOWNLOAD ARTIFACTS FOR 2024.10.7
# ─────────────────────────────────────────────────────────────────────────────
section "4. Downloading UiPath Automation Suite ${NEW_VERSION} artifacts"

mkdir -p "${WORKDIR}/roles"
cd "${WORKDIR}"

dl() {
  local url="$1" dest="$2" label="$3"
  info "Downloading ${label}..."
  # WGET_OPTS injects the proxy settings defined in the proxy configuration block above.
  # shellcheck disable=SC2086
  wget -q --show-progress ${WGET_OPTS} -O "${dest}" "${url}" \
    || die "Download failed: ${url}
  Check proxy connectivity: curl -x \"${HTTPS_PROXY}\" -I \"${url}\""
  ok "${label} -> ${dest}"
}

dl "https://download.uipath.com/uipathctl/${NEW_VERSION}/uipathctl-${NEW_VERSION}-linux-amd64.tar.gz" \
   "uipathctl.tar.gz" "uipathctl ${NEW_VERSION}"
tar -xzf uipathctl.tar.gz && chmod +x uipathctl && rm -f uipathctl.tar.gz
ok "uipathctl ready: $(./uipathctl version 2>/dev/null | head -1 || echo 'extracted OK')"

dl "https://download.uipath.com/automation-suite/${NEW_VERSION}/versions.json" \
   "versions.json" "versions.json"

dl "https://download.uipath.com/automation-suite/${NEW_VERSION}/embedded-helm-charts.txt" \
   "as-helm-charts.txt" "embedded-helm-charts.txt"

dl "https://download.uipath.com/automation-suite/${NEW_VERSION}/openshift-images.txt" \
   "as-images.txt" "openshift-images.txt"

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 5 - RECREATE NAMESPACE, SERVICE ACCOUNT AND RBAC
# ─────────────────────────────────────────────────────────────────────────────
section "5. Recreating namespace, Service Account and RBAC"

oc get namespace "${NAMESPACE}" &>/dev/null || oc new-project "${NAMESPACE}"
oc get namespace "${ISTIO_NS}"  &>/dev/null || oc new-project "${ISTIO_NS}"
oc project "${NAMESPACE}"
ok "Active project: ${NAMESPACE}"

if oc get serviceaccount uipathadmin -n "${NAMESPACE}" &>/dev/null; then
  info "ServiceAccount 'uipathadmin' already exists."
else
  oc create serviceaccount uipathadmin -n "${NAMESPACE}"
  ok "ServiceAccount 'uipathadmin' created."
fi

oc create rolebinding uipathadmin-admin \
  --clusterrole=admin --serviceaccount="${NAMESPACE}:uipathadmin" \
  -n "${NAMESPACE}" 2>/dev/null && ok "RoleBinding 'uipathadmin-admin' created." \
  || info "RoleBinding already exists."

cd "${WORKDIR}/roles"

# Write and apply all RBAC manifests
cat > rbac-all.yaml <<EOF
---
# SA.1: allow the installer to patch the UiPath namespace labels/annotations
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: uipathadmin-limited
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  resourceNames: ["${NAMESPACE}"]
  verbs: ["get", "patch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: uipathadmin-limited-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: uipathadmin-limited
subjects:
- kind: ServiceAccount
  name: uipathadmin
  namespace: ${NAMESPACE}
---
# SA.2: read the istio-system namespace (installer checks it exists)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: uipathadmin-namespace-reader
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  resourceNames: ["${ISTIO_NS}"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: uipathadmin-namespace-reader-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: uipathadmin-namespace-reader
subjects:
- kind: ServiceAccount
  name: uipathadmin
  namespace: ${NAMESPACE}
---
# SA.3: create / list namespaces (installer may create sub-namespaces)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: namespace-creator
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["create", "get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: uipathadmin-namespace-creator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: namespace-creator
subjects:
- kind: ServiceAccount
  name: uipathadmin
  namespace: ${NAMESPACE}
---
# List nodes, CRDs, PriorityClasses (pre-install checks)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: list-nodes-and-crd-clusterrole
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list", "get"]
- apiGroups: ["apiextensions.k8s.io"]
  resources: ["customresourcedefinitions"]
  verbs: ["list"]
- apiGroups: ["metrics.k8s.io"]
  resources: ["nodes"]
  verbs: ["list", "get"]
- apiGroups: ["scheduling.k8s.io"]
  resources: ["priorityclasses"]
  verbs: ["get"]
---
# Generic namespace read (used by various installer checks)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: namespace-reader-clusterrole
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get"]
---
# Full control over Istio CRDs cluster-wide (WASM plugin + networking)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: istio-crd-admin-role
rules:
- apiGroups: ["networking.istio.io", "extensions.istio.io"]
  resources: ["*"]
  verbs: ["*"]
---
# Core namespaced role in the UiPath namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: uipath-automationsuite-role
  namespace: ${NAMESPACE}
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles", "rolebindings"]
  verbs: ["*"]
- apiGroups: ["*"]
  resources: ["secrets", "configmaps"]
  verbs: ["get", "watch", "list", "patch", "update", "create"]
- apiGroups: ["security.istio.io", "networking.istio.io"]
  resources: ["*"]
  verbs: ["*"]
---
# Namespaced role in istio-system (WASM plugin configuration)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: istio-system-automationsuite-role
  namespace: ${ISTIO_NS}
rules:
- apiGroups: [""]
  resources: ["services", "pods"]
  verbs: ["list"]
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles", "rolebindings"]
  verbs: ["*"]
- apiGroups: ["*"]
  resources: ["secrets", "configmaps"]
  verbs: ["get", "watch", "list", "patch", "update", "create"]
- apiGroups: ["networking.istio.io", "extensions.istio.io"]
  resources: ["*"]
  verbs: ["*"]
---
# Fix for PeerAuthentication / AuthorizationPolicy in the UiPath namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: uipathadmin-istio-permissions
  namespace: ${NAMESPACE}
rules:
- apiGroups: ["security.istio.io"]
  resources: ["*"]
  verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: uipathadmin-istio-permissions
  namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
  name: uipathadmin
  namespace: ${NAMESPACE}
roleRef:
  kind: Role
  name: uipathadmin-istio-permissions
  apiGroup: rbac.authorization.k8s.io
EOF

oc apply -f rbac-all.yaml && ok "All RBAC manifests applied from rbac-all.yaml."

# Additional bindings that must be created via CLI (not via apply to avoid conflicts)
oc create clusterrolebinding list-nodes-and-crd-rolebinding \
  --clusterrole=list-nodes-and-crd-clusterrole \
  --serviceaccount="${NAMESPACE}:uipathadmin" 2>/dev/null \
  && ok "CRB 'list-nodes-and-crd-rolebinding' created." || info "Already exists."

oc create rolebinding uipath-automationsuite-rolebinding \
  --role=uipath-automationsuite-role \
  --serviceaccount="${NAMESPACE}:uipathadmin" \
  -n "${NAMESPACE}" 2>/dev/null \
  && ok "RB 'uipath-automationsuite-rolebinding' created." || info "Already exists."

oc create rolebinding istio-system-automationsuite-rolebinding \
  --role=istio-system-automationsuite-role \
  --serviceaccount="${NAMESPACE}:uipathadmin" \
  -n "${ISTIO_NS}" 2>/dev/null \
  && ok "RB 'istio-system-automationsuite-rolebinding' in ${ISTIO_NS} created." || info "Already exists."

# Admin rolebinding in istio-system (required for WASM plugin management)
oc -n "${ISTIO_NS}" create rolebinding uipadmin-istio-system-admin \
  --clusterrole=admin \
  --serviceaccount="${NAMESPACE}:uipathadmin" 2>/dev/null \
  && ok "Admin RB in ${ISTIO_NS} created." || info "Already exists."

oc create clusterrolebinding istio-crd-admin-rolebinding \
  --clusterrole=istio-crd-admin-role \
  --serviceaccount="${NAMESPACE}:uipathadmin" 2>/dev/null \
  && ok "CRB 'istio-crd-admin-rolebinding' created." || info "Already exists."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 6 - GENERATE KUBECONFIG FOR THE SA
# ─────────────────────────────────────────────────────────────────────────────
section "6. Generating kubeconfig for ServiceAccount 'uipathadmin'"

cd "${WORKDIR}"
SA_TOKEN=$(oc -n "${NAMESPACE}" create token uipathadmin --duration=8760h)
API_SERVER=$(oc config view -o jsonpath="{.clusters[0].cluster.server}")

# Try with TLS verify first, fall back to insecure (self-signed clusters)
oc login --server="${API_SERVER}" --token="${SA_TOKEN}" \
  --kubeconfig="${KUBECONFIG_PATH}" 2>/dev/null \
|| oc login --server="${API_SERVER}" --token="${SA_TOKEN}" \
  --kubeconfig="${KUBECONFIG_PATH}" --insecure-skip-tls-verify=true

ok "kubeconfig saved: ${KUBECONFIG_PATH}"

oc get namespace "${NAMESPACE}" --kubeconfig="${KUBECONFIG_PATH}" &>/dev/null \
  && ok "kubeconfig validated - SA 'uipathadmin' can reach the cluster." \
  || die "kubeconfig validation failed. Check SA token and API server connectivity."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 7 - GENERATE input.json
# ─────────────────────────────────────────────────────────────────────────────
section "7. Generating input.json"

cat > "${WORKDIR}/input.json" <<EOF
{
  "kubernetes_distribution": "openshift",
  "infra": {
    "docker_registry": {
      "username": "${REGISTRY_USER}",
      "password": "${REGISTRY_PASS}"
    }
  },
  "install_type": "online",
  "profile": "ha",
  "fqdn": "${FQDN}",
  "istioMinProtocolVersion": "TLSV1_2",
  "admin_username": "${ADMIN_USER}",
  "admin_password": "${ADMIN_PASSWORD}",
  "telemetry_optout": true,
  "fips_enabled_nodes": false,
  "disable_presigned_url": false,
  "external_object_storage": {
    "enabled": true,
    "create_bucket": false,
    "storage_type": "s3",
    "fqdn": "${OBS_HOST}",
    "port": ${OBS_PORT},
    "region": "",
    "access_key": "${OBS_ACCESS_KEY}",
    "secret_key": "${OBS_SECRET_KEY}",
    "use_instance_profile": false,
    "bucket_name_prefix": "${OBC_NAME}",
    "bucket_name_suffix": ""
  },
  "ingress": {
    "gateway_selector": { "istio": "ingressgateway" },
    "namespace": "${ISTIO_NS}"
  },
  "exclude_components": [
    "argocd", "istio", "monitoring", "logging", "gatekeeper",
    "dapr", "velero", "alerts", "cert-manager", "network-policies"
  ],
  "server_certificate": {
    "tls_cert_file": "", "tls_key_file": "", "ca_cert_file": ""
  },
  "additional_ca_certs": "",
  "sql": {
    "create_db": true,
    "server_url": "${SQL_HOST}",
    "port": "${SQL_PORT}",
    "username": "${SQL_USER}",
    "password": "${SQL_PASSWORD}"
  },
  "orchestrator":          { "enabled": true  },
  "processmining":         { "enabled": false },
  "insights":              { "enabled": false, "enable_realtime_monitoring": false },
  "automation_hub":        { "enabled": false },
  "automation_ops":        { "enabled": true  },
  "aicenter":              { "enabled": false },
  "documentunderstanding": { "enabled": false, "modernProjects": { "enabled": false } },
  "test_manager":          { "enabled": false },
  "action_center":         { "enabled": true  },
  "apps":                  { "enabled": false },
  "dataservice":           { "enabled": false },
  "asrobots":              { "enabled": false },
  "storage_class":                 "ceph-bucket",
  "storage_class_single_replica":  "ceph-bucket",
  "platform":              { "enabled": true  },
  "namespace":             "${NAMESPACE}",
  "sql_connection_string_template":
    "Server=tcp:${SQL_HOST},${SQL_PORT};Initial Catalog=DB_NAME_PLACEHOLDER;Persist Security Info=False;User Id=${SQL_USER};Password='${SQL_PASSWORD}';MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;Max Pool Size=100;",
  "sql_connection_string_template_jdbc":
    "jdbc:sqlserver://${SQL_HOST}:${SQL_PORT};database=DB_NAME_PLACEHOLDER;user=${SQL_USER};password={REDACTED};encrypt=true;trustServerCertificate=true;loginTimeout=30;hostNameInCertificate=${SQL_HOST_FQDN}",
  "sql_connection_string_template_odbc":
    "SERVER=${SQL_HOST},${SQL_PORT};DATABASE=DB_NAME_PLACEHOLDER;DRIVER={ODBC Driver 17 for SQL Server};UID=${SQL_USER};PWD={REDACTED};Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=30;hostNameInCertificate=${SQL_HOST_FQDN}",
  "sql_connection_string_template_sqlalchemy_pyodbc":
    "mssql+pyodbc://${SQL_USER}:REDACTED@${SQL_HOST}:${SQL_PORT}/DB_NAME_PLACEHOLDER?driver=ODBC+Driver+17+for+SQL+Server"
}
EOF

jq empty "${WORKDIR}/input.json" \
  && ok "input.json is valid JSON. Saved: ${WORKDIR}/input.json" \
  || die "input.json syntax error. Run: jq . ${WORKDIR}/input.json"

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 8 - PREREQ CREATE (SQL databases + object storage validation)
# ─────────────────────────────────────────────────────────────────────────────
section "8. Running prereq create"

cd "${WORKDIR}"
info "Creating SQL databases and validating object storage access..."
./uipathctl prereq create input.json \
  --versions versions.json \
  --kubeconfig "${KUBECONFIG_PATH}" \
  --log-level debug \
  || die "prereq create failed. Review errors above and re-run."
ok "prereq create completed."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 9 - PREREQ RUN (cluster environment validation)
# ─────────────────────────────────────────────────────────────────────────────
section "9. Running prereq run (cluster validation)"

info "Validating cluster prerequisites (nodes, resources, connectivity)..."
./uipathctl prereq run input.json \
  --versions versions.json \
  --kubeconfig "${KUBECONFIG_PATH}" \
  --log-level debug \
  || die "prereq run failed. Fix reported issues before proceeding."
ok "prereq run passed - cluster is ready."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 10 - INSTALL (manifest apply)
# ─────────────────────────────────────────────────────────────────────────────
section "10. Installing UiPath Automation Suite ${NEW_VERSION}"

confirm "All checks passed. Type 'yes' to start the installation (30-60 min)"

info "Running manifest apply..."
./uipathctl manifest apply input.json \
  --versions versions.json \
  --kubeconfig "${KUBECONFIG_PATH}" \
  --log-level info \
  || die "manifest apply failed. Review errors. You can safely re-run manifest apply to resume."
ok "manifest apply completed."

# ─────────────────────────────────────────────────────────────────────────────
# SECTION 11 - POST-INSTALL HEALTH CHECK
# ─────────────────────────────────────────────────────────────────────────────
section "11. Post-install health check"

info "Running health check..."
./uipathctl health check --kubeconfig "${KUBECONFIG_PATH}" --log-level info

echo ""
info "Pod status in '${NAMESPACE}':"
oc get pods -n "${NAMESPACE}" --kubeconfig "${KUBECONFIG_PATH}" -o wide \
  | grep -v "^NAME\|Completed" || true

echo ""
info "ArgoCD Application status:"
oc get applications.argoproj.io -n "${NAMESPACE}" \
  --kubeconfig "${KUBECONFIG_PATH}" \
  -o custom-columns="APP:.metadata.name,SYNC:.status.sync.status,HEALTH:.status.health.status" \
  2>/dev/null \
  || helm list -n "${NAMESPACE}" --kubeconfig "${KUBECONFIG_PATH}" 2>/dev/null \
  || true

echo ""
info "UiPath pods remaining in '${ISTIO_NS}' (should be empty):"
oc get pods -n "${ISTIO_NS}" 2>/dev/null | grep -iE "uipath|${NAMESPACE}" \
  || echo "  None found. Good."

echo ""
ok "=========================================================="
ok "  UiPath Automation Suite ${NEW_VERSION} - INSTALL DONE"
ok "  URL  : https://${FQDN}"
ok "  Admin: ${ADMIN_USER}"
ok "  Log  : ${LOG_FILE}"
ok "=========================================================="

# Clean up old logs (keep last 10 runs)
ls -t "${LOG_DIR}"/uipath-install_*.log 2>/dev/null \
  | tail -n +11 | xargs -r rm -f 2>/dev/null || true
ls -t "${LOG_DIR}"/uipath-diag_*.tar.gz 2>/dev/null \
  | tail -n +11 | xargs -r rm -f 2>/dev/null || true