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

if [ "$#" -ne 8 ]; then
  echo "usage: $0 <host-kustomize-path> <api-kustomize-path> <namespace> <host-kubeconfig> <host-context> <admin-kubeconfig> <image> <image-pull-secret>" >&2
  exit 2
fi

host_kustomize_path=$1
api_kustomize_path=$2
namespace=$3
host_kubeconfig=$4
host_context=$5
admin_kubeconfig=$6
image=$7
image_pull_secret=$8
apply_script=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/apply-kustomize-images.sh

case "$namespace" in
  ""|*[!a-z0-9-]*)
    echo "webhook namespace must be DNS-compatible: $namespace" >&2
    exit 2
    ;;
esac

apply_args=(
  "$host_kustomize_path"
  "$namespace"
  "$host_kubeconfig"
  "$host_context"
  "cp9.local/dedicated-webhook-server:placeholder=$image"
)

if [ "${CP9_RENDER_ONLY:-false}" = "true" ]; then
  if [ -n "$image_pull_secret" ]; then
    CP9_IMAGE_PULL_SECRET="$image_pull_secret" "$apply_script" "${apply_args[@]}"
  else
    "$apply_script" "${apply_args[@]}"
  fi
  kubectl kustomize "$api_kustomize_path"
  exit 0
fi

tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/cp9-dedicated-webhook.XXXXXX")
cleanup() {
  rm -rf "$tmpdir"
}
trap cleanup EXIT

host_kubectl=(kubectl --kubeconfig "$host_kubeconfig" --context "$host_context")
dedicated_kubectl=(kubectl --kubeconfig "$admin_kubeconfig" --context cp9-dedicated)
secret_name=webhook-server-cert
probe_manifest="$api_kustomize_path/cluster-admission-probe.yaml"

load_reusable_certificate() {
  secret_json="$tmpdir/existing-secret.json"
  "${host_kubectl[@]}" --namespace "$namespace" get secret "$secret_name" -o json >"$secret_json" 2>/dev/null || return 1

  python3 - "$secret_json" "$tmpdir" >/dev/null 2>&1 <<'PY' || return 1
import base64
import json
import pathlib
import sys

data = json.load(open(sys.argv[1])).get("data") or {}
output_dir = pathlib.Path(sys.argv[2])
for name in ("ca.crt", "tls.crt", "tls.key"):
    value = data.get(name)
    if not isinstance(value, str) or not value:
        raise SystemExit(1)
    (output_dir / name).write_bytes(base64.b64decode(value, validate=True))
PY

  openssl x509 -checkend 86400 -noout -in "$tmpdir/ca.crt" >/dev/null 2>&1 || return 1
  openssl x509 -checkend 86400 -noout -in "$tmpdir/tls.crt" >/dev/null 2>&1 || return 1
  openssl verify -CAfile "$tmpdir/ca.crt" "$tmpdir/ca.crt" >/dev/null 2>&1 || return 1
  openssl verify -CAfile "$tmpdir/ca.crt" "$tmpdir/tls.crt" >/dev/null 2>&1 || return 1

  python3 - "$tmpdir/tls.crt" "$namespace" >/dev/null 2>&1 <<'PY' || return 1
import ssl
import sys

certificate = ssl._ssl._test_decode_cert(sys.argv[1])
names = {
    value
    for kind, value in certificate.get("subjectAltName", ())
    if kind == "DNS"
}
namespace = sys.argv[2]
expected = {
    "webhook-server",
    f"webhook-server.{namespace}",
    f"webhook-server.{namespace}.svc",
    f"webhook-server.{namespace}.svc.cluster.local",
}
if not expected.issubset(names):
    raise SystemExit(1)
PY

  cmp -s \
    <(openssl x509 -pubkey -noout -in "$tmpdir/tls.crt" 2>/dev/null) \
    <(openssl pkey -in "$tmpdir/tls.key" -passin pass: -pubout 2>/dev/null)
}

if load_reusable_certificate; then
  echo "reusing existing Dedicated webhook certificate"
else
  openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 7 \
    -subj "/CN=cp9-dedicated-webhook-ca" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,keyCertSign,digitalSignature,keyEncipherment" \
    -keyout "$tmpdir/ca.key" -out "$tmpdir/ca.crt" >/dev/null 2>&1

  cat >"$tmpdir/server.ext" <<EOF
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:webhook-server,DNS:webhook-server.$namespace,DNS:webhook-server.$namespace.svc,DNS:webhook-server.$namespace.svc.cluster.local
EOF

  openssl req -new -newkey rsa:2048 -sha256 -nodes \
    -subj "/CN=webhook-server.$namespace.svc" \
    -keyout "$tmpdir/tls.key" -out "$tmpdir/tls.csr" >/dev/null 2>&1
  openssl x509 -req -in "$tmpdir/tls.csr" \
    -CA "$tmpdir/ca.crt" -CAkey "$tmpdir/ca.key" -CAcreateserial \
    -days 7 -sha256 -extfile "$tmpdir/server.ext" -out "$tmpdir/tls.crt" >/dev/null 2>&1
  openssl verify -CAfile "$tmpdir/ca.crt" "$tmpdir/tls.crt" >/dev/null

  "${host_kubectl[@]}" --namespace "$namespace" create secret generic "$secret_name" \
    --type=kubernetes.io/tls \
    --from-file=ca.crt="$tmpdir/ca.crt" \
    --from-file=tls.crt="$tmpdir/tls.crt" \
    --from-file=tls.key="$tmpdir/tls.key" \
    --dry-run=client -o yaml |
    "${host_kubectl[@]}" apply --server-side --force-conflicts -f - >/dev/null
fi

if [ -n "$image_pull_secret" ]; then
  CP9_IMAGE_PULL_SECRET="$image_pull_secret" "$apply_script" "${apply_args[@]}"
else
  "$apply_script" "${apply_args[@]}"
fi

"${dedicated_kubectl[@]}" apply --server-side --force-conflicts -k "$api_kustomize_path" >/dev/null

ca_bundle=$(python3 - "$tmpdir/ca.crt" <<'PY'
import base64
import pathlib
import sys

print(base64.b64encode(pathlib.Path(sys.argv[1]).read_bytes()).decode())
PY
)

patched_configs=0
for kind in mutatingwebhookconfiguration validatingwebhookconfiguration; do
  while IFS= read -r object_name; do
    [ -n "$object_name" ] || continue
    webhook_count=$("${dedicated_kubectl[@]}" get "$object_name" -o json | python3 -c '
import json
import sys

print(len((json.load(sys.stdin).get("webhooks") or [])))
')
    patch_file="$tmpdir/ca-bundle-patch.json"
    python3 - "$ca_bundle" "$webhook_count" >"$patch_file" <<'PY'
import json
import sys

ca_bundle, count = sys.argv[1], int(sys.argv[2])
json.dump(
    [
        {
            "op": "add",
            "path": f"/webhooks/{index}/clientConfig/caBundle",
            "value": ca_bundle,
        }
        for index in range(count)
    ],
    sys.stdout,
)
PY
    "${dedicated_kubectl[@]}" patch "$object_name" --type=json --patch-file "$patch_file" >/dev/null
    patched_configs=$((patched_configs + 1))
  done < <("${dedicated_kubectl[@]}" get "$kind" \
    --selector=cp9.tidbcloud.com/dedicated-webhook=true -o name)
done

if [ "$patched_configs" -eq 0 ]; then
  echo "no Dedicated webhook configurations were applied" >&2
  exit 1
fi

cert_stamp=$(python3 - "$tmpdir/ca.crt" "$tmpdir/tls.crt" <<'PY'
import hashlib
import pathlib
import sys

digest = hashlib.sha256()
for name in sys.argv[1:]:
    digest.update(pathlib.Path(name).read_bytes())
print(digest.hexdigest())
PY
)
deployment_patch=$(printf '{"spec":{"template":{"metadata":{"annotations":{"cp9.tidbcloud.com/webhook-cert-sha256":"%s"}}}}}' "$cert_stamp")
"${host_kubectl[@]}" --namespace "$namespace" patch deployment/webhook-server \
  --type=merge -p "$deployment_patch" >/dev/null
"${host_kubectl[@]}" --namespace "$namespace" rollout status deployment/webhook-server --timeout=180s

"${dedicated_kubectl[@]}" get mutatingwebhookconfiguration \
  --selector=cp9.tidbcloud.com/dedicated-webhook=true -o json >"$tmpdir/mutating-webhooks.json"
python3 - "$ca_bundle" "$tmpdir/mutating-webhooks.json" <<'PY'
import json
import sys

expected = sys.argv[1]
items = json.load(open(sys.argv[2])).get("items") or []
if not items or any(
    webhook.get("clientConfig", {}).get("caBundle") != expected
    for item in items
    for webhook in item.get("webhooks") or []
):
    raise SystemExit("Dedicated mutating webhook CA bundle verification failed")
PY

# Deployment readiness can precede Service endpoint and kube-proxy convergence.
admission_ready=false
for attempt in {1..30}; do
  if "${dedicated_kubectl[@]}" --namespace "$namespace" create --dry-run=server \
    -f "$probe_manifest" -o json >"$tmpdir/cluster-admission-probe.json" 2>"$tmpdir/cluster-admission-probe.err"; then
    admission_ready=true
    break
  fi
  sleep 1
done
if [ "$admission_ready" != true ]; then
  cat "$tmpdir/cluster-admission-probe.err" >&2
  exit 1
fi
python3 - "$tmpdir/cluster-admission-probe.json" <<'PY'
import json
import sys

object_ = json.load(open(sys.argv[1]))
for_provider = (object_.get("spec") or {}).get("forProvider")
if not isinstance(for_provider, dict):
    raise SystemExit("Dedicated Cluster admission did not default spec.forProvider")
if for_provider.get("apiVersion") != "cloud.pingcap.com/v1":
    raise SystemExit("Dedicated Cluster admission returned the wrong spec.forProvider apiVersion")
if for_provider.get("kind") != "Unknown":
    raise SystemExit("Dedicated Cluster admission returned the wrong spec.forProvider kind")
PY
