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

CLINIC_REPO="pingcap-inc/clinic"
APP_DELIVERY_REPO="tidbcloud/app-delivery"
WORKFLOW="clinic_cd.yml"
REF="master"
ENVIRONMENT="prod-cloud"
COMPONENT="clinic"
KUBE_CONTEXT="arn:aws:eks:us-west-2:557537366020:cluster/clinic-prod"
KUBE_NAMESPACE="clinic-prod-cloud"
KUBE_DEPLOYMENT="clinic"
AUTO_YES=0
SKIP_APPROVE=0
SKIP_MERGE=0
POLL_SECONDS=30
ROLL_OUT_TIMEOUT_SECONDS=180
MAX_WAIT_SECONDS=1800

usage() {
  cat <<'EOF'
Usage:
  tools/release_clinic_prod_cloud.sh [--yes]

Automates the prod-cloud Clinic server release:
  1. Trigger pingcap-inc/clinic Clinic CD with env=prod-cloud, components=clinic.
  2. Wait for the workflow to build and push the image.
  3. Find the generated tidbcloud/app-delivery PR by exact image tag.
  4. Validate that the PR only updates prod-cloud Clinic kustomization.
  5. Approve, wait for checks, merge, then wait for the live deployment rollout.

This helper intentionally has no prod-op deployment path.

Options:
  --yes                 Run without the confirmation prompt.
  --ref master          Clinic git ref to deploy. Only master is accepted.
  --skip-approve        Do not approve the app-delivery PR.
  --skip-merge          Do not merge the app-delivery PR.
  --poll-seconds N      Poll interval. Default: 30.
  --max-wait-seconds N  Max wait for each major phase. Default: 1800.
  -h, --help            Show this help.
EOF
}

log() {
  printf '[%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >&2
}

die() {
  printf 'error: %s\n' "$*" >&2
  exit 1
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --yes)
      AUTO_YES=1
      shift
      ;;
    --ref)
      REF="${2:-}"
      [[ -n "$REF" ]] || die "--ref requires a value"
      shift 2
      ;;
    --skip-approve)
      SKIP_APPROVE=1
      shift
      ;;
    --skip-merge)
      SKIP_MERGE=1
      shift
      ;;
    --poll-seconds)
      POLL_SECONDS="${2:-}"
      [[ "$POLL_SECONDS" =~ ^[0-9]+$ ]] || die "--poll-seconds must be an integer"
      shift 2
      ;;
    --max-wait-seconds)
      MAX_WAIT_SECONDS="${2:-}"
      [[ "$MAX_WAIT_SECONDS" =~ ^[0-9]+$ ]] || die "--max-wait-seconds must be an integer"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      die "unknown argument: $1"
      ;;
  esac
done

[[ "$ENVIRONMENT" == "prod-cloud" ]] || die "refusing to deploy environment other than prod-cloud"
[[ "$COMPONENT" == "clinic" ]] || die "refusing to deploy component other than clinic"
[[ "$REF" == "master" ]] || die "refusing to deploy non-master ref to prod-cloud"

require_cmd gh
require_cmd jq
require_cmd kubectl
require_cmd grep
require_cmd sed

TARGET_SHA="$(gh api "repos/${CLINIC_REPO}/commits/${REF}" --jq '.sha')"
[[ -n "$TARGET_SHA" ]] || die "failed to resolve ${CLINIC_REPO}@${REF}"
IMAGE_TAG="master-${TARGET_SHA}"
TARGET_IMAGE="557537366020.dkr.ecr.us-west-2.amazonaws.com/clinic:${IMAGE_TAG}"

cat <<EOF
Release target:
  clinic repo:      ${CLINIC_REPO}
  ref:              ${REF}
  sha:              ${TARGET_SHA}
  workflow:         ${WORKFLOW}
  environment:      ${ENVIRONMENT}
  component:        ${COMPONENT}
  image:            ${TARGET_IMAGE}
  app-delivery:     ${APP_DELIVERY_REPO}
  kube context:     ${KUBE_CONTEXT}
  kube namespace:   ${KUBE_NAMESPACE}
  kube deployment:  ${KUBE_DEPLOYMENT}

This helper will not deploy prod-op.
EOF

if [[ "$AUTO_YES" -ne 1 ]]; then
  printf 'Continue? Type "prod-cloud" to confirm: '
  read -r confirmation
  [[ "$confirmation" == "prod-cloud" ]] || die "confirmation failed"
fi

wait_for_run() {
  local repo="$1"
  local run_id="$2"
  local label="$3"
  local elapsed=0

  while (( elapsed <= MAX_WAIT_SECONDS )); do
    local json status conclusion url
    json="$(gh run view "$run_id" --repo "$repo" --json status,conclusion,url)"
    status="$(jq -r '.status' <<<"$json")"
    conclusion="$(jq -r '.conclusion // ""' <<<"$json")"
    url="$(jq -r '.url' <<<"$json")"

    log "${label}: status=${status} conclusion=${conclusion:-pending} ${url}"

    if [[ "$status" == "completed" ]]; then
      [[ "$conclusion" == "success" ]] || die "${label} failed with conclusion=${conclusion}"
      return 0
    fi

    sleep "$POLL_SECONDS"
    elapsed=$((elapsed + POLL_SECONDS))
  done

  die "timed out waiting for ${label}"
}

checks_ready() {
  jq -e '
    .statusCheckRollup
    | length > 0
    and all(.[]; .status == "COMPLETED" and ((.conclusion // "") | IN("SUCCESS", "SKIPPED", "NEUTRAL")))
  ' >/dev/null
}

wait_for_pr_checks() {
  local pr="$1"
  local elapsed=0

  while (( elapsed <= MAX_WAIT_SECONDS )); do
    local json
    json="$(gh pr view "$pr" --repo "$APP_DELIVERY_REPO" --json statusCheckRollup,mergeable,reviewDecision,state,url)"
    gh pr view "$pr" --repo "$APP_DELIVERY_REPO" --json statusCheckRollup \
      --jq '.statusCheckRollup[] | [.name, .status, (.conclusion // "pending")] | @tsv' \
      | sed 's/^/  check: /'

    if checks_ready <<<"$json"; then
      log "app-delivery PR checks are ready"
      return 0
    fi

    sleep "$POLL_SECONDS"
    elapsed=$((elapsed + POLL_SECONDS))
  done

  die "timed out waiting for app-delivery PR checks"
}

validate_app_delivery_pr() {
  local pr="$1"
  local title files
  title="$(gh pr view "$pr" --repo "$APP_DELIVERY_REPO" --json title --jq '.title')"
  [[ "$title" == *"clinic in prod-cloud to ${IMAGE_TAG}"* ]] || die "unexpected app-delivery PR title: ${title}"

  files="$(gh pr diff "$pr" --repo "$APP_DELIVERY_REPO" --name-only)"
  [[ "$files" == "clinic/clinic-api/envs/prod-cloud/kustomization.yaml" ]] \
    || die "unexpected app-delivery PR files: ${files}"

  gh pr diff "$pr" --repo "$APP_DELIVERY_REPO" --patch | grep -q "newTag: ${IMAGE_TAG}" \
    || die "app-delivery PR does not set newTag: ${IMAGE_TAG}"
}

wait_for_app_delivery_pr() {
  local elapsed=0

  while (( elapsed <= MAX_WAIT_SECONDS )); do
    local pr
    pr="$(gh pr list --repo "$APP_DELIVERY_REPO" --state open --search "$IMAGE_TAG" --json number --jq '.[0].number // ""')"
    if [[ -n "$pr" ]]; then
      printf '%s\n' "$pr"
      return 0
    fi

    log "waiting for app-delivery PR for ${IMAGE_TAG}"
    sleep "$POLL_SECONDS"
    elapsed=$((elapsed + POLL_SECONDS))
  done

  die "timed out waiting for app-delivery PR"
}

wait_for_rollout() {
  local elapsed=0

  while (( elapsed <= MAX_WAIT_SECONDS )); do
    local image
    image="$(kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" get deploy "$KUBE_DEPLOYMENT" -o jsonpath='{.spec.template.spec.containers[*].image}')"
    log "live deployment image: ${image}"

    if [[ "$image" == "$TARGET_IMAGE" ]]; then
      kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" rollout status "deploy/${KUBE_DEPLOYMENT}" --timeout="${ROLL_OUT_TIMEOUT_SECONDS}s"
      return 0
    fi

    sleep "$POLL_SECONDS"
    elapsed=$((elapsed + POLL_SECONDS))
  done

  die "timed out waiting for live deployment image ${TARGET_IMAGE}"
}

log "triggering Clinic CD: env=${ENVIRONMENT}, components=${COMPONENT}, ref=${REF}"
dispatch_output="$(gh workflow run "$WORKFLOW" --repo "$CLINIC_REPO" --ref "$REF" -f "env=${ENVIRONMENT}" -f "components=${COMPONENT}")"
printf '%s\n' "$dispatch_output"

run_id="$(grep -Eo 'actions/runs/[0-9]+' <<<"$dispatch_output" | sed 's#actions/runs/##' | tail -n1)"
if [[ -z "$run_id" ]]; then
  log "workflow command did not return a run URL; polling recent runs"
  sleep 5
  run_id="$(gh run list --repo "$CLINIC_REPO" --workflow "$WORKFLOW" --limit 20 --json databaseId,displayTitle,headSha,status \
    --jq ".[] | select(.headSha == \"${TARGET_SHA}\" and .displayTitle == \"Deploy ${COMPONENT} to ${ENVIRONMENT}\") | .databaseId" \
    | head -n1)"
fi
[[ -n "$run_id" ]] || die "failed to locate Clinic CD run"

wait_for_run "$CLINIC_REPO" "$run_id" "Clinic CD run ${run_id}"

app_pr="$(wait_for_app_delivery_pr)"
log "app-delivery PR: https://github.com/${APP_DELIVERY_REPO}/pull/${app_pr}"
validate_app_delivery_pr "$app_pr"

if [[ "$SKIP_APPROVE" -ne 1 ]]; then
  log "approving app-delivery PR ${app_pr}"
  gh pr review "$app_pr" --repo "$APP_DELIVERY_REPO" --approve -b "Approve prod-cloud clinic bump to ${IMAGE_TAG}."
fi

wait_for_pr_checks "$app_pr"

if [[ "$SKIP_MERGE" -ne 1 ]]; then
  log "merging app-delivery PR ${app_pr}"
  gh pr merge "$app_pr" --repo "$APP_DELIVERY_REPO" --squash --delete-branch
else
  die "stopping before merge because --skip-merge was set"
fi

wait_for_rollout

kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" get deploy "$KUBE_DEPLOYMENT" \
  -o jsonpath='{.spec.template.spec.containers[*].image}{"\n"}{.metadata.generation}{"\n"}{.status.observedGeneration}{"\n"}{.status.readyReplicas}{"/"}{.spec.replicas}{"\n"}'
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" get pods -l app=clinic -o wide

log "prod-cloud clinic release complete: ${TARGET_IMAGE}"
