#!/usr/bin/env bash
# cloudtaser installer — https://cloudtaser.io
# Usage:
#   curl -fsSL https://docs.cloudtaser.io/install.sh | bash                   # install CLI binary only (default)
#   curl -fsSL https://docs.cloudtaser.io/install.sh | bash -s -- --full      # install CLI + deploy Helm chart
#   curl -fsSL https://docs.cloudtaser.io/install.sh | bash -s -- --demo      # install CLI + Helm + OpenBao + demo pods
set -euo pipefail

NAMESPACE="cloudtaser-system"
DEMO_NS="cloudtaser-demo"
VAULT_NS="cloudtaser-vault"
CHART_REPO="https://charts.cloudtaser.io"
CLI_RELEASE_BASE="https://releases.cloudtaser.io/cli/latest"
CLI_RELEASE_KEY="https://releases.cloudtaser.io/keys/release.pub"
COSIGN_VERSION="3.1.1"
COSIGN_RELEASE_BASE="https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}"
CLI_ONLY=false
FULL=false
DEMO=false
COSIGN_BOOTSTRAPPED=false
DEMO_ROOT_TOKEN="$(openssl rand -hex 16)"

info()  { echo -e "\033[1;34m==>\033[0m $*"; }
ok()    { echo -e "\033[1;32m  ✓\033[0m $*"; }
warn()  { echo -e "\033[1;33m  !\033[0m $*"; }
fail()  { echo -e "\033[1;31m  ✗\033[0m $*"; exit 1; }
sha256_file() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    fail "sha256sum or shasum is required to verify the downloaded binary"
  fi
}
ensure_cosign() {
  if COSIGN_BIN="$(command -v cosign 2>/dev/null)"; then
    return
  fi

  # Pinned from the official v${COSIGN_VERSION}/cosign_checksums.txt release asset.
  # Update the version and all four hashes together.
  case "${OS}/${ARCH}" in
    darwin/amd64) COSIGN_EXPECTED_SHA="14d2678dfbfde18798151e86fbd91ebdadbb7424b18412a42a155dd8a2df4c7a" ;;
    darwin/arm64) COSIGN_EXPECTED_SHA="94b42a9e697be95675f6160ab031a9a5f1ec1e646d6f648d7b2f5cd59ececbc5" ;;
    linux/amd64) COSIGN_EXPECTED_SHA="ae1ecd212663f3693ad9edf8b1a183900c9a52d3155ba6e354237f9a0f6463fc" ;;
    linux/arm64) COSIGN_EXPECTED_SHA="2ec865872e331c32fd12b08dae15332d3f92c0aa029219589684a4903ca85d11" ;;
    *) fail "Automatic cosign setup is unsupported on ${OS}/${ARCH}; install cosign from https://docs.sigstore.dev/cosign/installation/" ;;
  esac

  COSIGN_ASSET="cosign-${OS}-${ARCH}"
  COSIGN_BIN="${INSTALL_TMPDIR}/cosign"
  info "cosign not found; downloading verified cosign v${COSIGN_VERSION} for this install"
  curl -fsSL --retry 3 "${COSIGN_RELEASE_BASE}/${COSIGN_ASSET}" -o "$COSIGN_BIN"
  COSIGN_ACTUAL_SHA="$(sha256_file "$COSIGN_BIN")"
  [ "$COSIGN_ACTUAL_SHA" = "$COSIGN_EXPECTED_SHA" ] || fail "checksum mismatch for downloaded cosign v${COSIGN_VERSION}"
  chmod 0755 "$COSIGN_BIN"
  COSIGN_BOOTSTRAPPED=true
  ok "Downloaded and verified temporary cosign v${COSIGN_VERSION}"
}

for arg in "$@"; do
  case "$arg" in
    --demo) DEMO=true ;;
    --full) FULL=true ;;
    --cli-only) CLI_ONLY=true ;;
    --cleanup)
      info "Uninstalling cloudtaser"
      helm uninstall cloudtaser -n cloudtaser-system 2>/dev/null || true
      kubectl delete namespace cloudtaser-system cloudtaser-demo cloudtaser-vault --ignore-not-found 2>/dev/null
      kubectl delete clusterrolebinding vault-tokenreview cloudtaser-operator cloudtaser-ebpf --ignore-not-found 2>/dev/null
      kubectl delete clusterrole cloudtaser-operator cloudtaser-ebpf --ignore-not-found 2>/dev/null
      kubectl delete mutatingwebhookconfiguration cloudtaser-operator-webhook --ignore-not-found 2>/dev/null
      kubectl delete validatingwebhookconfiguration cloudtaser-operator-webhook-validating --ignore-not-found 2>/dev/null
      kubectl delete validatingadmissionpolicy cloudtaser-require-image-digest --ignore-not-found 2>/dev/null || true
      kubectl delete validatingadmissionpolicybinding cloudtaser-require-image-digest --ignore-not-found 2>/dev/null || true
      kubectl delete crd \
        cloudtaserconfigs.api.cloudtaser.io \
        secretmappings.api.cloudtaser.io \
        debugreports.api.cloudtaser.io \
        cloudtaserpolicies.api.cloudtaser.io \
        --ignore-not-found 2>/dev/null
      ok "cloudtaser uninstalled"
      exit 0
      ;;
    --help|-h)
      echo "cloudtaser installer"
      echo ""
      echo "Usage:"
      echo "  curl -fsSL https://docs.cloudtaser.io/install.sh | bash                   # CLI binary only (default)"
      echo "  curl -fsSL https://docs.cloudtaser.io/install.sh | bash -s -- --full      # CLI + Helm chart deploy"
      echo "  curl -fsSL https://docs.cloudtaser.io/install.sh | bash -s -- --demo      # CLI + Helm + OpenBao + demo pods"
      echo ""
      echo "Options:"
      echo "  --full      Install CLI binary and deploy the cloudtaser Helm chart to your cluster"
      echo "  --demo      Install CLI binary, deploy Helm chart, OpenBao vault, and demo workload"
      echo "  --cli-only  Explicit alias for the default: install CLI binary only"
      echo "  --cleanup   Uninstall cloudtaser and remove all resources"
      echo "  --help      Show this help"
      exit 0
      ;;
  esac
done

# ── CLI binary install (always) ────────────────────────────────────────
info "Installing cloudtaser-cli"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$ARCH" in
  x86_64|amd64) ARCH="amd64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  *) fail "Unsupported architecture: $ARCH" ;;
esac
BINARY="cloudtaser-cli-${OS}-${ARCH}"
INSTALL_DIR="${CLOUDTASER_INSTALL_DIR:-/usr/local/bin}"
INSTALL_TMPDIR="$(mktemp -d)"
trap 'rm -rf "$INSTALL_TMPDIR"' EXIT
ensure_cosign
info "Downloading $BINARY"
curl -fsSL "${CLI_RELEASE_BASE}/${BINARY}" -o "${INSTALL_TMPDIR}/${BINARY}"
curl -fsSL "${CLI_RELEASE_BASE}/SHA256SUMS" -o "${INSTALL_TMPDIR}/SHA256SUMS"
curl -fsSL "$CLI_RELEASE_KEY" -o "${INSTALL_TMPDIR}/release.pub"
if curl -fsSL "${CLI_RELEASE_BASE}/SHA256SUMS.sigstore.json" -o "${INSTALL_TMPDIR}/SHA256SUMS.sigstore.json" 2>/dev/null; then
  SIGSTORE_BUNDLE="${INSTALL_TMPDIR}/SHA256SUMS.sigstore.json"
else
  SIGSTORE_BUNDLE=""
  curl -fsSL "${CLI_RELEASE_BASE}/SHA256SUMS.sig" -o "${INSTALL_TMPDIR}/SHA256SUMS.sig"
fi
info "Verifying release checksums"
if [ -n "$SIGSTORE_BUNDLE" ]; then
  if ! "$COSIGN_BIN" verify-blob \
    --key "${INSTALL_TMPDIR}/release.pub" \
    --bundle "$SIGSTORE_BUNDLE" \
    "${INSTALL_TMPDIR}/SHA256SUMS" >/dev/null; then
    fail "cosign signature verification failed for SHA256SUMS"
  fi
else
  info "Transparency-log bundle unavailable for this legacy release; verifying the pinned key signature instead"
  COSIGN_VERIFY_STDERR="${INSTALL_TMPDIR}/cosign-verify.stderr"
  if ! "$COSIGN_BIN" verify-blob \
    --key "${INSTALL_TMPDIR}/release.pub" \
    --insecure-ignore-sct \
    --insecure-ignore-tlog \
    --signature "${INSTALL_TMPDIR}/SHA256SUMS.sig" \
    "${INSTALL_TMPDIR}/SHA256SUMS" >/dev/null 2>"$COSIGN_VERIFY_STDERR"; then
    cat "$COSIGN_VERIFY_STDERR" >&2
    fail "cosign signature verification failed for SHA256SUMS"
  fi
  while IFS= read -r cosign_line; do
    case "$cosign_line" in
      *"Skipping tlog verification is an insecure practice"*) ;;
      "Flag --signature has been deprecated"*) ;;
      "Verified OK") ;;
      *) printf '%s\n' "$cosign_line" >&2 ;;
    esac
  done <"$COSIGN_VERIFY_STDERR"
fi
EXPECTED_SHA="$(awk -v binary="$BINARY" '$2 == binary {print $1}' "${INSTALL_TMPDIR}/SHA256SUMS")"
[ -n "$EXPECTED_SHA" ] || fail "SHA256SUMS does not contain $BINARY"
ACTUAL_SHA="$(sha256_file "${INSTALL_TMPDIR}/${BINARY}")"
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || fail "checksum mismatch for $BINARY"
ok "Verified cosign signature and checksum"
if [ -w "$INSTALL_DIR" ]; then
  install -m 0755 "${INSTALL_TMPDIR}/${BINARY}" "${INSTALL_DIR}/cloudtaser-cli"
else
  sudo install -m 0755 "${INSTALL_TMPDIR}/${BINARY}" "${INSTALL_DIR}/cloudtaser-cli"
fi
ok "Installed to ${INSTALL_DIR}/cloudtaser-cli"

# Component-install commands (for example `source install port`) invoke cosign
# after this script exits to verify container images. Preserve only the pinned,
# checksum-verified bootstrap binary; a cosign already supplied by the operator
# remains untouched.
if $COSIGN_BOOTSTRAPPED; then
  if [ -w "$INSTALL_DIR" ]; then
    install -m 0755 "$COSIGN_BIN" "${INSTALL_DIR}/cosign"
  else
    sudo install -m 0755 "$COSIGN_BIN" "${INSTALL_DIR}/cosign"
  fi
  ok "Installed verified cosign v${COSIGN_VERSION} to ${INSTALL_DIR}/cosign"
fi

# ── Default (no --full / --demo): print summary and exit ──────────────
if ! $FULL && ! $DEMO; then
  echo ""
  echo "  Version:  $(${INSTALL_DIR}/cloudtaser-cli version 2>/dev/null || echo 'unknown')"
  echo "  Docs:     https://docs.cloudtaser.io"
  echo ""
  echo "  To deploy to a Kubernetes cluster:"
  echo "    curl -fsSL https://docs.cloudtaser.io/install.sh | bash -s -- --full"
  echo ""
  exit 0
fi

# ── Prerequisites ──────────────────────────────────────────────────────
info "Checking prerequisites"
command -v kubectl >/dev/null 2>&1 || fail "kubectl not found — install from https://kubernetes.io/docs/tasks/tools/"
command -v helm >/dev/null 2>&1    || fail "helm not found — install from https://helm.sh/docs/tasks/tools/"
kubectl cluster-info >/dev/null 2>&1 || fail "Cannot reach Kubernetes cluster — check your kubeconfig"
ok "kubectl and helm available, cluster reachable"

# ── Helm repo ──────────────────────────────────────────────────────────
info "Adding cloudtaser Helm repo"
helm repo add cloudtaser "$CHART_REPO" --force-update >/dev/null 2>&1
helm repo update cloudtaser >/dev/null 2>&1
ok "Helm repo added: $CHART_REPO"

# ── Demo mode: deploy OpenBao vault ────────────────────────────────────
if $DEMO; then
  info "Demo mode: deploying OpenBao vault in-cluster"

  kubectl create namespace "$VAULT_NS" 2>/dev/null || true

  # Deploy OpenBao in dev mode
  cat <<VAULT_EOF | kubectl -n "$VAULT_NS" apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: vault
  labels:
    app: vault
spec:
  containers:
    - name: vault
      image: openbao/openbao:2.5.2
      args: ["server", "-dev", "-dev-root-token-id=$DEMO_ROOT_TOKEN", "-dev-listen-address=0.0.0.0:8200"]
      ports:
        - containerPort: 8200
      env:
        - name: VAULT_DEV_ROOT_TOKEN_ID
          value: "$DEMO_ROOT_TOKEN"
      readinessProbe:
        httpGet:
          path: /v1/sys/health
          port: 8200
        initialDelaySeconds: 5
        periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: vault
spec:
  selector:
    app: vault
  ports:
    - port: 8200
      targetPort: 8200
VAULT_EOF

  info "Waiting for OpenBao to be ready"
  kubectl -n "$VAULT_NS" wait --for=condition=Ready pod/vault --timeout=120s >/dev/null 2>&1
  ok "OpenBao running at vault.$VAULT_NS.svc:8200"

  # Configure vault: enable KV, write demo secrets, enable K8s auth
  info "Configuring OpenBao: secrets, auth, policies"

  # Token review binding
  kubectl create clusterrolebinding vault-tokenreview \
    --clusterrole=system:auth-delegator \
    --serviceaccount="$VAULT_NS":default 2>/dev/null || true

  kubectl -n "$VAULT_NS" exec vault -- sh -c '
    export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=$VAULT_DEV_ROOT_TOKEN_ID
    vault secrets enable -path=secret kv-v2 2>/dev/null || true
    vault kv put secret/db/credentials \
      username=cloudtaser_user \
      password=S3cur3-D3m0-P@ss! \
      host=db.eu-west.example.com \
      port=5432
    vault auth enable kubernetes 2>/dev/null || true
    vault write auth/kubernetes/config \
      kubernetes_host=https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}
    vault policy write cloudtaser-demo - <<POLICY
path "secret/data/db/*" { capabilities = ["read"] }
path "auth/token/create" { capabilities = ["update"] }
POLICY
    vault write auth/kubernetes/role/demo \
      bound_service_account_names=default \
      bound_service_account_namespaces=cloudtaser-demo \
      policies=cloudtaser-demo \
      ttl=1h
  ' >/dev/null 2>&1

  ok "OpenBao configured: secret/db/credentials, role=demo"
  VAULT_ADDR="http://vault.$VAULT_NS.svc:8200"
else
  # CLOUDTASER_VAULT_ADDRESS is the legacy env var; CLOUDTASER_SECRETSTORE_ADDRESS
  # is preferred. Read the new name first, fall back to the legacy name.
  VAULT_ADDR="${CLOUDTASER_SECRETSTORE_ADDRESS:-${CLOUDTASER_VAULT_ADDRESS:-}}"
  if [ -z "$VAULT_ADDR" ]; then
    warn "No secret store address specified"
    warn "Set CLOUDTASER_SECRETSTORE_ADDRESS or use --demo for an in-cluster secret store"
    warn "Installing without a secret store address — set it later via helm upgrade"
    VAULT_ADDR=""
  fi
fi

# ── Install cloudtaser ─────────────────────────────────────────────────
info "Installing cloudtaser"

# Remove webhooks before upgrade — AKS/GKE admission enforcers modify them,
# causing Helm field ownership conflicts on upgrade.
kubectl delete mutatingwebhookconfiguration cloudtaser-operator-webhook --ignore-not-found >/dev/null 2>&1
kubectl delete validatingwebhookconfiguration cloudtaser-operator-webhook-validating --ignore-not-found >/dev/null 2>&1

HELM_ARGS=(
  cloudtaser cloudtaser/cloudtaser
  --namespace "$NAMESPACE" --create-namespace
  --set ebpf.enabled=true
  --set ebpf.enforceMode=true
)

# In --demo mode, the secret store runs in-cluster so we set
# operator.secretstore.address directly. For production, the default is beacon
# mode (P2P relay) — use cloudtaser-cli to configure the beacon connection
# instead of setting operator.secretstore.address here.
# See: https://docs.cloudtaser.io/architecture/beacon-relay/
if [ -n "$VAULT_ADDR" ]; then
  HELM_ARGS+=(--set "operator.secretstore.address=$VAULT_ADDR")
fi

HELM_OUTPUT=$(helm upgrade --install "${HELM_ARGS[@]}" 2>&1)
if [ $? -ne 0 ]; then
  echo "$HELM_OUTPUT" | grep -i "error\|failed" | head -3
  fail "Helm install failed"
fi
ok "cloudtaser installed in namespace $NAMESPACE"

info "Waiting for operator"
if ! kubectl -n "$NAMESPACE" wait --for=condition=Available deployment/cloudtaser-operator --timeout=180s >/dev/null 2>&1; then
  warn "Operator not ready — checking pods:"
  kubectl -n "$NAMESPACE" get pods 2>/dev/null
  fail "Operator deployment did not become available within 180s"
fi
ok "Operator ready"

# Check eBPF
EBPF_READY=$(kubectl -n "$NAMESPACE" get daemonset cloudtaser-ebpf -o jsonpath='{.status.numberReady}' 2>/dev/null || echo 0)
EBPF_DESIRED=$(kubectl -n "$NAMESPACE" get daemonset cloudtaser-ebpf -o jsonpath='{.status.desiredNumberScheduled}' 2>/dev/null || echo 0)
ok "eBPF agents: $EBPF_READY/$EBPF_DESIRED nodes"

# ── Demo mode: deploy test pods ───────────────────────────────────────
if $DEMO; then
  info "Deploying demo workload with secret injection"

  kubectl create namespace "$DEMO_NS" 2>/dev/null || true

  # Wait for webhook
  for i in $(seq 1 30); do
    kubectl get mutatingwebhookconfiguration cloudtaser-operator-webhook >/dev/null 2>&1 && break
    sleep 1
  done
  sleep 3

  cat <<DEMO_EOF | kubectl -n "$DEMO_NS" apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: demo-app
  labels:
    app: demo-app
  annotations:
    cloudtaser.io/inject: "true"
    cloudtaser.io/ebpf: "true"
    cloudtaser.io/secretstore-address: "$VAULT_ADDR"
    cloudtaser.io/secretstore-role: "demo"
    cloudtaser.io/secret-paths: "secret/data/db/credentials"
    cloudtaser.io/env-map: "username=DB_USER,password=DB_PASSWORD,host=DB_HOST,port=DB_PORT"
spec:
  containers:
    - name: app
      image: busybox:1
      securityContext:
        readOnlyRootFilesystem: false
      command: ["sh", "-c"]
      args:
        - |
          echo "=== cloudtaser Demo ==="
          echo "DB_USER:     \${DB_USER:-NOT SET}"
          echo "DB_PASSWORD: \$([ -n \"\$DB_PASSWORD\" ] && echo '********' || echo 'NOT SET')"
          echo "DB_HOST:     \${DB_HOST:-NOT SET}"
          echo "DB_PORT:     \${DB_PORT:-NOT SET}"
          echo ""
          echo "Secrets fetched from EU vault, delivered via memfd_secret."
          echo "Try: kubectl exec demo-app -n $DEMO_NS -- cat /proc/1/environ"
          echo "     (wrapper's environ is structurally clean: secrets enter only after exec)"
          echo "     (child's environ is protected by eBPF kprobe on openat)"
          sleep infinity
DEMO_EOF

  info "Waiting for demo pod"
  if kubectl -n "$DEMO_NS" wait --for=condition=Ready pod/demo-app --timeout=180s >/dev/null 2>&1; then
    ok "Demo pod running"
  else
    warn "Demo pod not ready yet — check: kubectl -n $DEMO_NS describe pod demo-app"
  fi
fi

# ── Summary ────────────────────────────────────────────────────────────
echo ""
info "cloudtaser is running"
echo ""
echo "  Namespace:  $NAMESPACE"
echo "  Components: operator, webhook, eBPF agent ($EBPF_READY nodes)"
if [ -n "$VAULT_ADDR" ]; then
  echo "  Vault:      $VAULT_ADDR"
fi
echo ""

if $DEMO; then
  # WARNING: The root token is printed here for demo convenience only.
  # In production, use short-lived tokens and revoke the root token immediately
  # after initial configuration. Never expose or log root tokens in production.
  echo "  Root token: $DEMO_ROOT_TOKEN"
  echo "  Demo pod:   kubectl -n $DEMO_NS logs demo-app"
  echo "  Score:      kubectl -n $DEMO_NS logs demo-app | grep 'protection score'"
  echo "  Secrets:    kubectl -n $DEMO_NS exec demo-app -- cat /proc/1/environ"
  echo "              (should show NO secrets — they're in memfd_secret)"
  echo ""
fi

echo "  Docs:       https://docs.cloudtaser.io"
echo "  Annotate:   https://docs.cloudtaser.io/configuration/annotations/"
echo ""
