#!/bin/bash
# Helper functions

replace_token() {
  local token="$1"
  local value="$2"
  local file="$3"

  TOKEN="${token}" VALUE="${value}" perl -0pi -e 's/\Q$ENV{TOKEN}\E/$ENV{VALUE}/g' "${file}"
}

ops_api_url() {
  local server_url="$1"
  local path="$2"
  echo "${server_url%/}/ops/api/v1/${path#/}"
}

mint_ops_api_token() {
  if [[ -n "${OPS_API_ACCESS_TOKEN:-}" ]]; then
    echo "$OPS_API_ACCESS_TOKEN"
    return 0
  fi

  : "${AUTH0_CLIENT_ID:?AUTH0_CLIENT_ID is required}"
  : "${AUTH0_CLIENT_SECRET:?AUTH0_CLIENT_SECRET is required}"

  local resp access_token
  resp=$(curl --location --request POST 'https://tidb-soc2.us.auth0.com/oauth/token' \
    --header 'content-type: application/x-www-form-urlencoded' \
    --data-urlencode 'grant_type=client_credentials' \
    --data-urlencode "client_id=${AUTH0_CLIENT_ID}" \
    --data-urlencode "client_secret=${AUTH0_CLIENT_SECRET}" \
    --data-urlencode 'audience=https://tidb-soc2.us.auth0.com/api/v2/')

  access_token="$(echo "$resp" | jq -r '.access_token')"
  if [[ -z "$access_token" || "$access_token" == "null" ]]; then
    echo "Error: failed to mint ops API token" >&2
    return 1
  fi

  OPS_API_ACCESS_TOKEN="$access_token"
  echo "$OPS_API_ACCESS_TOKEN"
}

build_ops_api_headers() {
  local token="$1"
  local region_id="$2"
  local logical_region="$3"

  jq -n \
    --arg auth "Bearer ${token}" \
    --arg region_id "$region_id" \
    --arg logical_region "$logical_region" \
    '{
      "Authorization": $auth,
      "Content-Type": "application/json",
      "X-Region-Id": $region_id,
      "X-Logical-Region": $logical_region
    }'
}

ops_api_request() {
  local method="$1"
  local base_url="$2"
  local path="$3"
  local headers_json="$4"
  local body="${5:-}"
  local url
  url="$(ops_api_url "$base_url" "$path")"

  local curl_args=(
    --silent
    --show-error
    --fail
    --request "$method"
    "$url"
  )

  while IFS= read -r header; do
    curl_args+=(--header "$header")
  done < <(echo "$headers_json" | jq -r 'to_entries[] | "\(.key): \(.value)"')

  if [[ -n "$body" ]]; then
    curl_args+=(--data "$body")
  fi

  curl "${curl_args[@]}"
}

read_chart_version() {
  local chart_dir="$1"
  if [[ ! -f "$chart_dir/Chart.yaml" ]]; then
    echo "Error: Chart.yaml not found: $chart_dir/Chart.yaml" >&2
    return 1
  fi
  yq eval '.version' "$chart_dir/Chart.yaml"
}

HELM_UPGRADE_INSTALL_SERVER_SIDE_READY=""

require_helm_upgrade_install_server_side() {
  if [[ -n "$HELM_UPGRADE_INSTALL_SERVER_SIDE_READY" ]]; then
    return 0
  fi

  local helm_version_raw
  helm_version_raw="$(helm version --template '{{.Version}}' 2>/dev/null || helm version 2>/dev/null || true)"
  if [[ "$helm_version_raw" =~ v([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
    local major="${BASH_REMATCH[1]}"
    local minor="${BASH_REMATCH[2]}"
    local patch="${BASH_REMATCH[3]}"
    if (( major > 4 || (major == 4 && (minor > 0 || patch >= 5)) )); then
      HELM_UPGRADE_INSTALL_SERVER_SIDE_READY=1
      return 0
    fi
  fi

  echo "Error: Helm v4.0.5 or newer is required for helm upgrade --install --server-side. Found: ${helm_version_raw:-unknown}" >&2
  return 1
}

helm_adopt_apply() {
  local release="$1"
  local namespace="$2"
  local chart="$3"
  shift 3

  require_helm_upgrade_install_server_side || return $?
  helm upgrade --install --take-ownership --server-side=true --force-conflicts --create-namespace --debug \
    -n "$namespace" "$release" "$chart" "$@"
}

build_helmrelease_payload() {
  local name="$1"
  local namespace="$2"
  local component="$3"
  local values_file="$4"
  local version="$5"

  if [[ -z "$version" || "$version" == "latest" ]]; then
    echo "Error: exact chart version is required for $component" >&2
    return 1
  fi
  if [[ ! -f "$values_file" ]]; then
    echo "Error: Values file not found: $values_file" >&2
    return 1
  fi

  local values_json
  values_json="$(yq eval -o json "$values_file" | jq .)"

  jq -n \
    --arg name "$name" \
    --arg namespace "$namespace" \
    --arg component "$component" \
    --argjson config "$(jq -n --arg version "$version" --argjson values "$values_json" '{timeout: "10m", version: $version, values: $values}')" \
    '{name: $name, namespace: $namespace, component: $component, config: $config}'
}

# Helper function to check if HelmRelease exists
helmrelease_exists() {
  local server_url="$1"
  local name="$2"
  local region_id="${3:-}"
  local logical_region="${4:-}"

  if [[ -n "$region_id" && -n "$logical_region" ]]; then
    local headers_json
    mint_ops_api_token >/dev/null
    headers_json="$(build_ops_api_headers "$OPS_API_ACCESS_TOKEN" "$region_id" "$logical_region")"
    ops_api_request GET "$server_url" "helmreleases/${name}" "$headers_json" >/dev/null 2>&1
    return $?
  fi

  local api_url
  api_url="$(ops_api_url "$server_url" "helmreleases/${name}")"
  local response
  response=$(curl -s -w "%{http_code}" -o /dev/null "$api_url" || echo "000")
  [[ "$response" == "200" ]]
}

# Helper function to create or update HelmRelease
create_or_update_helmrelease() {
  local server_url="$1"
  local name="$2"
  local namespace="$3"
  local component="$4"
  local values_file="$5"
  local version="$6"
  local region_id="${7:-}"
  local logical_region="${8:-}"

  echo "Processing HelmRelease: $name"

  local payload
  if ! payload="$(build_helmrelease_payload "$name" "$namespace" "$component" "$values_file" "$version")"; then
    echo "Error: Failed to build payload for $name"
    return 1
  fi

  local temp_response
  temp_response="$(mktemp)"
  local http_code
  local api_url

  if [[ -n "$region_id" && -n "$logical_region" ]]; then
    local headers_json
    mint_ops_api_token >/dev/null
    headers_json="$(build_ops_api_headers "$OPS_API_ACCESS_TOKEN" "$region_id" "$logical_region")"

    if helmrelease_exists "$server_url" "$name" "$region_id" "$logical_region"; then
      echo "Updating existing HelmRelease: $name"
      api_url="$(ops_api_url "$server_url" "helmreleases/${name}")"
      http_code=$(ops_api_request PUT "$server_url" "helmreleases/${name}" "$headers_json" "$payload" >"$temp_response" && echo 200 || echo 500)
    else
      echo "Creating new HelmRelease: $name"
      api_url="$(ops_api_url "$server_url" "helmreleases" )"
      http_code=$(ops_api_request POST "$server_url" "helmreleases" "$headers_json" "$payload" >"$temp_response" && echo 200 || echo 500)
    fi
  elif helmrelease_exists "$server_url" "$name"; then
    echo "Updating existing HelmRelease: $name"
    api_url="$(ops_api_url "$server_url" "helmreleases/${name}")"
    http_code=$(curl -s -w "%{http_code}" -o "$temp_response" \
      -X PUT "$api_url" \
      -H "Content-Type: application/json" \
      -d "$payload")
  else
    echo "Creating new HelmRelease: $name"
    api_url="$(ops_api_url "$server_url" "helmreleases")"
    http_code=$(curl -s -w "%{http_code}" -o "$temp_response" \
      -X POST "$api_url" \
      -H "Content-Type: application/json" \
      -d "$payload")
  fi

  # Check response and handle errors
  if [[ "$http_code" =~ ^(20[0-9]|202)$ ]]; then
    echo "Success: HelmRelease $name processed successfully"
    jq . "$temp_response" 2>/dev/null || cat "$temp_response"
    rm -f "$temp_response"
  else
    echo "Error: Failed to process HelmRelease $name (HTTP $http_code)"
    cat "$temp_response"
    rm -f "$temp_response"
    return 1
  fi
}

# Resolve the ops-server API URL from ingress host in a values file.
# Returns "<scheme>://<host>" if ingress.hosts[0].host is set, or empty string.
resolve_ops_server_url() {
  local values_file="$1"
  local host scheme tls_host tls_secret
  host="$(yq eval '.ingress.hosts[0].host // ""' "$values_file" 2>/dev/null || true)"
  if [[ -z "$host" || "$host" == "null" ]]; then
    return 0
  fi
  scheme="http"
  tls_host="$(yq eval '.ingress.tls[0].hosts[0] // ""' "$values_file" 2>/dev/null || true)"
  tls_secret="$(yq eval '.ingress.tls[0].secretName // ""' "$values_file" 2>/dev/null || true)"
  if [[ -n "$tls_host" || -n "$tls_secret" ]]; then
    scheme="https"
  fi
  echo "${scheme}://${host}"
}

default_globalserver_base_url() {
  local env_name="$1"
  case "$env_name" in
    dev)
      echo "https://www.gs.us-west-2.aws.observability-dev.pingcap.cloud"
      ;;
    staging)
      echo "https://www.gs.us-west-2.aws.observability-staging.pingcap.cloud"
      ;;
    prod)
      echo "https://www.gs.us-west-2.aws.observability.tidbcloud.com"
      ;;
    *)
      echo "Error: unsupported env for managed ops URL: $env_name" >&2
      return 1
      ;;
  esac
}

resolve_managed_ops_api_base_url() {
  local env_name="$1"
  default_globalserver_base_url "$env_name"
}

BOOTSTRAP_OPS_API_LOCAL_PORT="${BOOTSTRAP_OPS_API_LOCAL_PORT:-18180}"
BOOTSTRAP_OPS_API_PID=""
BOOTSTRAP_OPS_API_LOG=""

append_exit_trap() {
  local trap_command="$1"
  local existing_trap

  existing_trap="$(trap -p EXIT)"
  if [[ -n "$existing_trap" ]]; then
    existing_trap="${existing_trap#trap -- \'}"
    existing_trap="${existing_trap%\' EXIT}"
    if [[ "$existing_trap" == *"$trap_command"* ]]; then
      return 0
    fi
    trap "${existing_trap}"$'\n'"${trap_command}" EXIT
    return 0
  fi

  trap "$trap_command" EXIT
}

bootstrap_ops_api_is_running() {
  [[ -n "${BOOTSTRAP_OPS_API_PID:-}" ]] && kill -0 "$BOOTSTRAP_OPS_API_PID" >/dev/null 2>&1
}

cleanup_bootstrap_ops_api() {
  if bootstrap_ops_api_is_running; then
    kill "$BOOTSTRAP_OPS_API_PID"
    wait "$BOOTSTRAP_OPS_API_PID" 2>/dev/null || true
  fi
  BOOTSTRAP_OPS_API_PID=""
  if [[ -n "${BOOTSTRAP_OPS_API_LOG:-}" ]]; then
    rm -f "$BOOTSTRAP_OPS_API_LOG"
  fi
  BOOTSTRAP_OPS_API_LOG=""
}

wait_for_bootstrap_ops_server_deployment() {
  local timeout="${1:-5m}"
  kubectl -n ops-system rollout status deployment/ops-server --timeout="$timeout"
}

wait_for_bootstrap_helmrelease_ready() {
  local name="$1"
  local namespace="${2:-ops-system}"
  local timeout="${3:-10m}"

  kubectl -n "$namespace" wait --for=condition=Ready "helmrelease/${name}" --timeout="$timeout"
}

start_ops_server_port_forward() {
  local namespace="${1:-ops-system}"
  local service="${2:-ops-server}"
  local local_port="${3:-$BOOTSTRAP_OPS_API_LOCAL_PORT}"

  if bootstrap_ops_api_is_running; then
    return 0
  fi

  BOOTSTRAP_OPS_API_LOG="$(mktemp "${TMPDIR:-/tmp}/bootstrap-ops-api.XXXXXX")"
  kubectl -n "$namespace" port-forward "svc/${service}" "${local_port}:8180" >"$BOOTSTRAP_OPS_API_LOG" 2>&1 &
  BOOTSTRAP_OPS_API_PID=$!
  append_exit_trap cleanup_bootstrap_ops_api
}

wait_for_ops_api_ready() {
  local base_url="$1"
  local max_attempts="${2:-30}"

  for _ in $(seq 1 "$max_attempts"); do
    if curl --silent --show-error --fail "${base_url%/}/ops/api/v1/" >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done

  echo "Error: bootstrap ops API did not become reachable at ${base_url}" >&2
  return 1
}

prepare_bootstrap_ops_api() {
  if [[ "${BOOTSTRAP:-false}" != "true" ]]; then
    return 0
  fi

  if [[ "${SKIP_CLUSTER_ACCESS:-false}" == "true" ]]; then
    echo "Error: bootstrap=true requires cluster access; SKIP_CLUSTER_ACCESS must be false" >&2
    return 1
  fi

  if ! wait_for_bootstrap_ops_server_deployment; then
    return 1
  fi
  if ! start_ops_server_port_forward "ops-system" "ops-server" "$BOOTSTRAP_OPS_API_LOCAL_PORT"; then
    return 1
  fi
  if ! wait_for_ops_api_ready "http://127.0.0.1:${BOOTSTRAP_OPS_API_LOCAL_PORT}"; then
    return 1
  fi
  return 0
}

resolve_effective_ops_api_base_url() {
  local default_base_url="$1"

  if [[ "${BOOTSTRAP:-false}" != "true" ]]; then
    echo "$default_base_url"
    return 0
  fi

  if [[ "${SKIP_CLUSTER_ACCESS:-false}" == "true" ]]; then
    echo "Error: bootstrap=true requires cluster access; SKIP_CLUSTER_ACCESS must be false" >&2
    return 1
  fi

  if ! bootstrap_ops_api_is_running; then
    echo "Error: bootstrap ops API is not prepared; call prepare_bootstrap_ops_api first" >&2
    return 1
  fi

  echo "http://127.0.0.1:${BOOTSTRAP_OPS_API_LOCAL_PORT}"
}

ops_server_helmrelease_exists() {
  kubectl -n ops-system get helmrelease ops-server >/dev/null 2>&1
}

should_bootstrap_managed_deploy() {
  if ops_server_helmrelease_exists; then
    return 1
  fi
  return 0
}

wait_for_helmrelease_ready() {
  local base_url="$1"
  local name="$2"
  local region_id="$3"
  local logical_region="$4"
  local max_attempts="${5:-120}"
  local headers_json resp ready_status failures last_failure_reason

  if [[ "${BOOTSTRAP:-false}" == "true" && "$name" == "ops-server" && "$base_url" == "http://127.0.0.1:${BOOTSTRAP_OPS_API_LOCAL_PORT}" ]]; then
    if ! wait_for_bootstrap_helmrelease_ready "$name" "ops-system" "10m"; then
      return 1
    fi
    if ! prepare_bootstrap_ops_api; then
      return 1
    fi
    return 0
  fi

  mint_ops_api_token >/dev/null
  headers_json="$(build_ops_api_headers "$OPS_API_ACCESS_TOKEN" "$region_id" "$logical_region")"

  for _ in $(seq 1 "$max_attempts"); do
    resp="$(ops_api_request GET "$base_url" "helmreleases/${name}" "$headers_json")"
    ready_status="$(echo "$resp" | jq -r '.data.status.conditions[]? | select(.type == "Ready") | .status' | head -n 1)"
    if [[ "$ready_status" == "True" ]]; then
      return 0
    fi

    failures="$(echo "$resp" | jq -r '.data.status.failures // 0')"
    last_failure_reason="$(echo "$resp" | jq -r '.data.status.lastFailureReason // ""')"
    if [[ "$failures" != "0" && -n "$last_failure_reason" ]]; then
      echo "Error: HelmRelease ${name} failed with reason: ${last_failure_reason}" >&2
      return 1
    fi

    sleep 5
  done

  echo "Error: HelmRelease ${name} did not become Ready" >&2
  return 1
}

sed_inplace() {
    local pattern="$1"
    local replacement="$2"
    local file="$3"

    # Escape special characters in the replacement string for sed
    # Escape & and \ which have special meaning in sed replacement
    local escaped_replacement=$(printf '%s\n' "$replacement" | sed 's/[[\.*^$()+?{|]/\\&/g; s/&/\\&/g')

    sed -i.bak "s|${pattern}|${escaped_replacement}|g" "$file"
    rm -f "${file}.bak"
}
