#!/usr/bin/env bash
set -euo pipefail

ACTION="${1:-status}"
OPENBAO_NAMESPACE="${OPENBAO_NAMESPACE:-openbao}"
KUBECTL="${KUBECTL:-kubectl}"
INGRESS_NAME="${OPENBAO_PUBLIC_INGRESS_NAME:-openbao-ui-gateway}"
SERVICE_NAME="${OPENBAO_UI_SERVICE_NAME:-openbao-ui-gateway}"
DEPLOYMENT_NAME="${OPENBAO_UI_DEPLOYMENT_NAME:-openbao-ui-gateway}"
TUNNEL_NAME="${OPENBAO_UI_TUNNEL_NAME:-openbao-ui-railiance01}"
TUNNEL_URL="${OPENBAO_UI_BASE_URL:-http://127.0.0.1:18200}"
PUBLIC_URL="${OPENBAO_PUBLIC_URL:-https://bao.coulomb.social/}"
EXPECTED_CLUSTER_UID="${RAILIANCE01_CLUSTER_UID:-a553c742-0115-43d4-99a4-a5ca56fe0786}"
PRIVATE_MANIFEST="${OPENBAO_UI_OVERLAY_K8S:-helm/openbao-ui-overlay-k8s.yaml}"
ROLLBACK_MANIFEST="${OPENBAO_PUBLIC_INGRESS_ROLLBACK:-helm/openbao-public-ingress.rollback.yaml}"
MIDDLEWARE_MANIFEST="${OPENBAO_MIDDLEWARE:-helm/openbao-middleware.yaml}"
CONFIRM_RETRACT="RETRACT RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER"
CONFIRM_ROLLBACK="ROLLBACK RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER"

usage() {
  cat <<'USAGE'
Usage: scripts/openbao-public-listener-transition.sh status|dry-run|preflight|apply|rollback

`apply` requires both:
  OPENBAO_OPERATOR_LOGIN_VERIFIED=true
  OPENBAO_PUBLIC_LISTENER_CONFIRM='RETRACT RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER'

`rollback` requires:
  OPENBAO_PUBLIC_LISTENER_CONFIRM='ROLLBACK RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER'

The script never reads OpenBao credentials or secret values.
USAGE
}

fail() { printf '[ERR] %s\n' "$*" >&2; exit 1; }
ok() { printf '[OK] %s\n' "$*"; }

kube() {
  # KUBECTL may include an explicit --kubeconfig argument from Make.
  # shellcheck disable=SC2086
  $KUBECTL "$@"
}

require_cluster() {
  local observed
  observed="$(kube get namespace kube-system -o jsonpath='{.metadata.uid}')"
  [ "$observed" = "$EXPECTED_CLUSTER_UID" ] ||
    fail "cluster identity mismatch: expected $EXPECTED_CLUSTER_UID, observed $observed"
  ok "railiance01 cluster identity pinned"
}

require_private_source() {
  [ -f "$PRIVATE_MANIFEST" ] || fail "missing private gateway manifest: $PRIVATE_MANIFEST"
  if grep -Eq '^kind:[[:space:]]*Ingress[[:space:]]*$' "$PRIVATE_MANIFEST"; then
    fail "ordinary gateway manifest still contains an Ingress"
  fi
  ok "ordinary gateway manifest is Ingress-free"
}

require_private_runtime() {
  local service_type service_port
  service_type="$(kube -n "$OPENBAO_NAMESPACE" get service "$SERVICE_NAME" -o jsonpath='{.spec.type}')"
  service_port="$(kube -n "$OPENBAO_NAMESPACE" get service "$SERVICE_NAME" -o jsonpath='{.spec.ports[?(@.name=="http")].port}')"
  [ "$service_type" = "ClusterIP" ] || fail "gateway Service type is $service_type, not ClusterIP"
  [ "$service_port" = "8080" ] || fail "gateway Service http port is $service_port, not 8080"
  kube -n "$OPENBAO_NAMESPACE" rollout status "deployment/$DEPLOYMENT_NAME" --timeout=30s >/dev/null
  ok "gateway Deployment Ready behind ClusterIP"
}

require_tunnel() {
  command -v bridge >/dev/null || fail "bridge CLI not found"
  command -v jq >/dev/null || fail "jq not found"
  local report
  if ! report="$(bridge check "$TUNNEL_NAME" --json 2>/dev/null)"; then
    fail "named tunnel $TUNNEL_NAME is not lifecycle-healthy"
  fi
  jq -e --arg name "$TUNNEL_NAME" '.[] | select(.tunnel == $name and .ok == true)' \
    <<<"$report" >/dev/null || fail "named tunnel $TUNNEL_NAME did not report ok=true"
  curl -fsS --max-time 10 "$TUNNEL_URL/ui/platform-overlay/presets.json" >/dev/null ||
    fail "operator UI is not reachable through $TUNNEL_NAME"
  ok "named operator tunnel is healthy and reaches the UI"
}

status() {
  require_cluster
  require_private_source
  require_private_runtime
  if kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1; then
    printf '[INFO] public Ingress is present\n'
  else
    printf '[INFO] public Ingress is absent\n'
  fi
  require_tunnel
}

dry_run() {
  require_cluster
  require_private_source
  [ -f "$ROLLBACK_MANIFEST" ] || fail "missing rollback manifest: $ROLLBACK_MANIFEST"
  kube apply --server-side --dry-run=server -f "$PRIVATE_MANIFEST" >/dev/null
  kube apply --server-side --dry-run=server -f "$ROLLBACK_MANIFEST" >/dev/null
  kube -n "$OPENBAO_NAMESPACE" delete ingress "$INGRESS_NAME" --dry-run=server >/dev/null
  ok "private apply, rollback apply, and Ingress deletion pass server dry-run"
}

preflight() {
  status
  kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1 ||
    fail "public Ingress is already absent; use status"
  ok "public Ingress is present for attended transition"
}

apply_retraction() {
  [ "${OPENBAO_OPERATOR_LOGIN_VERIFIED:-false}" = "true" ] ||
    fail "attended operator login has not been explicitly verified"
  [ "${OPENBAO_PUBLIC_LISTENER_CONFIRM:-}" = "$CONFIRM_RETRACT" ] ||
    fail "exact retraction confirmation is missing"
  preflight
  kube -n "$OPENBAO_NAMESPACE" delete ingress "$INGRESS_NAME" --wait=true
  require_private_runtime
  require_tunnel
  if kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1; then
    fail "public Ingress still exists after deletion"
  fi
  local code
  code="$(curl -kLsS --max-time 10 -o /dev/null -w '%{http_code}' "$PUBLIC_URL" || true)"
  case "$code" in
    2??|3??) fail "public hostname still returns successful HTTP status $code" ;;
  esac
  ok "public Ingress absent; private gateway and named tunnel remain healthy"
}

rollback() {
  [ "${OPENBAO_PUBLIC_LISTENER_CONFIRM:-}" = "$CONFIRM_ROLLBACK" ] ||
    fail "exact rollback confirmation is missing"
  require_cluster
  [ -f "$MIDDLEWARE_MANIFEST" ] || fail "missing middleware manifest: $MIDDLEWARE_MANIFEST"
  [ -f "$ROLLBACK_MANIFEST" ] || fail "missing rollback manifest: $ROLLBACK_MANIFEST"
  kube apply -f "$MIDDLEWARE_MANIFEST"
  kube apply -f "$ROLLBACK_MANIFEST"
  kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null
  ok "prior public Ingress restored from rollback-only manifest"
}

case "$ACTION" in
  status) status ;;
  dry-run) dry_run ;;
  preflight) preflight ;;
  apply) apply_retraction ;;
  rollback) rollback ;;
  -h|--help|help) usage ;;
  *) usage >&2; exit 2 ;;
esac
