#!/usr/bin/env bash

# Copyright 2026 PingCAP, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELM="${HELM:-bin/helm}"
KUBECTL="${KUBECTL:-bin/kubectl}"
KUBECONFIG="${KUBECONFIG:-$(pwd)/kubeconfig}"
PLANNER_NAMESPACE="${PLANNER_NAMESPACE:-tidb-admin}"
PLANNER_DEPLOYMENT="${PLANNER_DEPLOYMENT:-capacity-planner-capacity-planner}"
QUERY_BIND_ADDRESS="${QUERY_BIND_ADDRESS:-:9090}"
QUERY_SERVICE="${QUERY_SERVICE:-capacity-planner-capacity-planner-query}"
QUERY_PORT="${QUERY_PORT:-9090}"
GRAFANA_CHART="${GRAFANA_CHART:-oci://ghcr.io/grafana-community/helm-charts/grafana}"
GRAFANA_CHART_VERSION="${GRAFANA_CHART_VERSION:-12.10.0}"
GRAFANA_RELEASE="${GRAFANA_RELEASE:-capacity-planner-grafana}"
GRAFANA_NAMESPACE="${GRAFANA_NAMESPACE:-monitoring}"
GRAFANA_LOAD_BALANCER_POOL="${GRAFANA_LOAD_BALANCER_POOL:-kind-grafana}"
GRAFANA_DASHBOARD="${GRAFANA_DASHBOARD:-${SCRIPT_DIR}/grafana-dashboard.json}"
GRAFANA_POST_RENDERER="${GRAFANA_POST_RENDERER:-${SCRIPT_DIR}/grafana-post-renderer.sh}"
DEPLOY_TIMEOUT="${DEPLOY_TIMEOUT:-5m}"

fail() {
	echo "deploy/grafana: $*" >&2
	exit 1
}

require_command() {
	local command_name="$1"
	if [[ "$command_name" == */* ]]; then
		[[ -x "$command_name" ]] || fail "command is not executable: ${command_name}"
	else
		command -v "$command_name" >/dev/null 2>&1 || fail "command is not executable: ${command_name}"
	fi
}

is_dns_label() {
	[[ "$1" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ && ${#1} -le 63 ]]
}

is_ipv4() {
	local address="$1"
	local first second third fourth extra octet
	IFS=. read -r first second third fourth extra <<<"$address"
	[[ -z "${extra:-}" && -n "${fourth:-}" ]] || return 1
	for octet in "$first" "$second" "$third" "$fourth"; do
		[[ "$octet" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
		((octet >= 0 && octet <= 255)) || return 1
	done
}

require_command "$HELM"
require_command "$KUBECTL"
require_command "$GRAFANA_POST_RENDERER"
[[ -r "$KUBECONFIG" ]] || fail "kubeconfig is not readable: ${KUBECONFIG}"
[[ -r "$GRAFANA_DASHBOARD" ]] || fail "Grafana dashboard is not readable: ${GRAFANA_DASHBOARD}"
is_dns_label "$PLANNER_NAMESPACE" || fail "PLANNER_NAMESPACE must be a DNS label: ${PLANNER_NAMESPACE}"
is_dns_label "$PLANNER_DEPLOYMENT" || fail "PLANNER_DEPLOYMENT must be a DNS label: ${PLANNER_DEPLOYMENT}"
is_dns_label "$QUERY_SERVICE" || fail "QUERY_SERVICE must be a DNS label: ${QUERY_SERVICE}"
is_dns_label "$GRAFANA_RELEASE" || fail "GRAFANA_RELEASE must be a DNS label: ${GRAFANA_RELEASE}"
is_dns_label "$GRAFANA_NAMESPACE" || fail "GRAFANA_NAMESPACE must be a DNS label: ${GRAFANA_NAMESPACE}"
is_dns_label "$GRAFANA_LOAD_BALANCER_POOL" ||
	fail "GRAFANA_LOAD_BALANCER_POOL must be a DNS label: ${GRAFANA_LOAD_BALANCER_POOL}"
[[ "$QUERY_PORT" =~ ^[1-9][0-9]*$ ]] && ((QUERY_PORT <= 65535)) ||
	fail "QUERY_PORT must be between 1 and 65535: ${QUERY_PORT}"
case "$QUERY_BIND_ADDRESS" in
":${QUERY_PORT}" | "0.0.0.0:${QUERY_PORT}") ;;
*) fail "QUERY_BIND_ADDRESS must expose QUERY_PORT on all Pod interfaces: ${QUERY_BIND_ADDRESS}" ;;
esac
[[ -n "$GRAFANA_CHART" && "$GRAFANA_CHART" != *[[:space:]]* ]] ||
	fail "GRAFANA_CHART must not be empty or contain whitespace"
[[ -n "$GRAFANA_CHART_VERSION" && "$GRAFANA_CHART_VERSION" != *[[:space:]]* ]] ||
	fail "GRAFANA_CHART_VERSION must not be empty or contain whitespace"

grafana_pool_address="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n metallb-system \
	get ipaddresspool "$GRAFANA_LOAD_BALANCER_POOL" \
	-o 'jsonpath={.spec.addresses[0]}')" ||
	fail "MetalLB pool ${GRAFANA_LOAD_BALANCER_POOL} is unavailable; run make deploy/kube first"
[[ "$grafana_pool_address" == */32 ]] ||
	fail "MetalLB pool ${GRAFANA_LOAD_BALANCER_POOL} must contain one IPv4 /32 address: ${grafana_pool_address:-empty}"
grafana_load_balancer_ip="${grafana_pool_address%/32}"
is_ipv4 "$grafana_load_balancer_ip" ||
	fail "MetalLB pool ${GRAFANA_LOAD_BALANCER_POOL} has an invalid IPv4 address: ${grafana_load_balancer_ip}"

planner_args="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$PLANNER_NAMESPACE" \
	get deployment "$PLANNER_DEPLOYMENT" \
	-o 'jsonpath={.spec.template.spec.containers[?(@.name=="capacity-planner")].args}')"
[[ " $planner_args " == *"--config=/etc/capacity-planner/capacity-planner.yaml"* ]] ||
	fail "${PLANNER_NAMESPACE}/${PLANNER_DEPLOYMENT} does not load the runtime config"
planner_instance="${PLANNER_DEPLOYMENT%-capacity-planner}"
planner_runtime_config="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$PLANNER_NAMESPACE" \
	get configmap \
	-l "app.kubernetes.io/instance=${planner_instance},app.kubernetes.io/name=capacity-planner" \
	-o 'jsonpath={range .items[*]}{.data.capacity-planner\.yaml}{end}')"
[[ "$planner_runtime_config" == *"bindAddress: ${QUERY_BIND_ADDRESS}"* ]] ||
	fail "${PLANNER_NAMESPACE}/${PLANNER_DEPLOYMENT} does not expose query engine at ${QUERY_BIND_ADDRESS}"

echo "deploy/grafana: exposing the planner query engine through ClusterIP ${PLANNER_NAMESPACE}/${QUERY_SERVICE}"
"$KUBECTL" --kubeconfig "$KUBECONFIG" apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: ${QUERY_SERVICE}
  namespace: ${PLANNER_NAMESPACE}
  labels:
    app.kubernetes.io/name: capacity-planner-query
    app.kubernetes.io/managed-by: capacity-planner-local-dev
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: capacity-planner
    app.kubernetes.io/instance: ${planner_instance}
  ports:
  - name: http-query
    port: ${QUERY_PORT}
    targetPort: ${QUERY_PORT}
    protocol: TCP
EOF

query_url="http://${QUERY_SERVICE}.${PLANNER_NAMESPACE}.svc.cluster.local:${QUERY_PORT}"
query_proxy_path="/api/v1/namespaces/${PLANNER_NAMESPACE}/services/http:${QUERY_SERVICE}:${QUERY_PORT}/proxy/-/ready"
query_ready=0
for ((attempt = 1; attempt <= 30; attempt++)); do
	if query_ready_response="$("$KUBECTL" --kubeconfig "$KUBECONFIG" get --raw "$query_proxy_path" 2>/dev/null)" &&
		[[ "$query_ready_response" == *"Ready"* ]]; then
		query_ready=1
		break
	fi
	sleep 1
done
((query_ready)) || fail "planner query Service did not become ready: ${query_url}"

values_dir="$(mktemp -d "${TMPDIR:-/tmp}/capacity-planner-grafana.XXXXXX")"
trap 'rm -rf "$values_dir"' EXIT
values_file="${values_dir}/values.yaml"
cat >"$values_file" <<EOF
testFramework:
  enabled: false
persistence:
  enabled: false
service:
  type: LoadBalancer
  loadBalancerIP: ${grafana_load_balancer_ip}
  annotations:
    metallb.io/address-pool: ${GRAFANA_LOAD_BALANCER_POOL}
resources:
  requests:
    cpu: 50m
    memory: 128Mi
dashboardProviders:
  dashboardproviders.yaml:
    apiVersion: 1
    providers:
    - name: default
      orgId: 1
      folder: Capacity Planner
      type: file
      disableDeletion: true
      editable: false
      updateIntervalSeconds: 10
      options:
        path: /var/lib/grafana/dashboards/default
datasources:
  datasources.yaml:
    apiVersion: 1
    deleteDatasources:
    - name: Capacity Planner
      orgId: 1
    datasources:
    - name: Capacity Planner
      uid: capacity-planner
      type: prometheus
      access: proxy
      url: ${query_url}
      isDefault: true
      editable: false
      jsonData:
        httpMethod: POST
        prometheusType: Prometheus
        prometheusVersion: 3.14.0
        timeInterval: 15s
EOF

echo "deploy/grafana: installing ${GRAFANA_RELEASE} with datasource ${query_url}"
"$HELM" --kubeconfig "$KUBECONFIG" upgrade --install \
	"$GRAFANA_RELEASE" "$GRAFANA_CHART" \
	--version "$GRAFANA_CHART_VERSION" \
	--namespace "$GRAFANA_NAMESPACE" \
	--create-namespace \
	--values "$values_file" \
	--set-file "dashboards.default.capacity-planner.json=${GRAFANA_DASHBOARD}" \
	--post-renderer "$GRAFANA_POST_RENDERER" \
	--wait --atomic --timeout "$DEPLOY_TIMEOUT"

grafana_deployment="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get deployment \
	-l "app.kubernetes.io/instance=${GRAFANA_RELEASE},app.kubernetes.io/name=grafana" \
	-o 'jsonpath={.items[0].metadata.name}')"
[[ -n "$grafana_deployment" ]] || fail "Grafana Deployment was not created"
"$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" rollout status \
	--timeout="$DEPLOY_TIMEOUT" "deployment/${grafana_deployment}"

grafana_service="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get service \
	-l "app.kubernetes.io/instance=${GRAFANA_RELEASE},app.kubernetes.io/name=grafana" \
	-o 'jsonpath={.items[0].metadata.name}')"
[[ -n "$grafana_service" ]] || fail "Grafana Service was not created"
grafana_service_type="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get service "$grafana_service" -o 'jsonpath={.spec.type}')"
[[ "$grafana_service_type" == "LoadBalancer" ]] ||
	fail "Grafana Service type is ${grafana_service_type:-empty}, expected LoadBalancer"
"$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" wait \
	--timeout="$DEPLOY_TIMEOUT" \
	--for="jsonpath={.status.loadBalancer.ingress[0].ip}=${grafana_load_balancer_ip}" \
	"service/${grafana_service}"
assigned_load_balancer_ip="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get service "$grafana_service" -o 'jsonpath={.status.loadBalancer.ingress[0].ip}')"
[[ "$assigned_load_balancer_ip" == "$grafana_load_balancer_ip" ]] ||
	fail "Grafana LoadBalancer has address ${assigned_load_balancer_ip:-none}, expected ${grafana_load_balancer_ip}"
allocate_node_ports="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get service "$grafana_service" -o 'jsonpath={.spec.allocateLoadBalancerNodePorts}')"
[[ "$allocate_node_ports" == "false" ]] ||
	fail "Grafana Service must disable LoadBalancer NodePort allocation"
assigned_node_port="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get service "$grafana_service" -o 'jsonpath={.spec.ports[0].nodePort}')"
[[ -z "$assigned_node_port" || "$assigned_node_port" == "0" ]] ||
	fail "Grafana LoadBalancer unexpectedly allocated NodePort ${assigned_node_port}"

grafana_proxy_path="/api/v1/namespaces/${GRAFANA_NAMESPACE}/services/http:${grafana_service}:80/proxy/api/health"
grafana_health="$("$KUBECTL" --kubeconfig "$KUBECONFIG" get --raw "$grafana_proxy_path")"
[[ "$grafana_health" == *'"database": "ok"'* || "$grafana_health" == *'"database":"ok"'* ]] ||
	fail "Grafana health endpoint did not report a healthy database"

datasource_config="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get configmap \
	-l "app.kubernetes.io/instance=${GRAFANA_RELEASE},app.kubernetes.io/name=grafana" \
	-o 'jsonpath={range .items[*]}{.data.datasources\.yaml}{end}')"
[[ "$datasource_config" == *"${query_url}"* ]] ||
	fail "Grafana datasource does not reference ${query_url}"

dashboard_config="$("$KUBECTL" --kubeconfig "$KUBECONFIG" -n "$GRAFANA_NAMESPACE" \
	get configmap \
	-l "app.kubernetes.io/instance=${GRAFANA_RELEASE},app.kubernetes.io/name=grafana,dashboard-provider=default" \
	-o 'jsonpath={range .items[*]}{.data.capacity-planner\.json}{end}')"
[[ "$dashboard_config" == *'"uid": "capacity-planner-metrics"'* ]] ||
	fail "Grafana Capacity Planner dashboard was not provisioned"
for metric_name in cpu memory pod_status_running pod_status_ready pod:tidb_server_query:rate1m; do
	[[ "$dashboard_config" == *"${metric_name}"* ]] ||
		fail "Grafana dashboard does not query ${metric_name}"
done

echo "deploy/grafana: ${GRAFANA_NAMESPACE}/${grafana_deployment} is ready"
echo "deploy/grafana: dashboard: /d/capacity-planner-metrics/capacity-planner-metrics"
echo "deploy/grafana: LoadBalancer URL: http://${grafana_load_balancer_ip}"
echo "deploy/grafana: if the host cannot route the Kind network, run 'make forward/grafana'"
echo "deploy/grafana: retrieve the admin password from Secret ${GRAFANA_NAMESPACE}/${GRAFANA_RELEASE}"
