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

PLAN_ONLY=0
if [[ "${1:-}" == "--plan" ]]; then
  PLAN_ONLY=1
  shift
fi
[[ $# -eq 5 ]] || {
  echo "usage: scripts/promote-release-image.sh [--plan] IMAGE PG_MAJOR TAG SHA EXPECTED_DIGEST" >&2
  exit 2
}
IMAGE="$1"
PG_MAJOR="$2"
TAG="$3"
SHA="$4"
EXPECTED_DIGEST="$5"

[[ "${IMAGE}" =~ ^[a-z0-9.-]+(/[a-z0-9._-]+)+$ ]] || {
  echo "IMAGE must be a lowercase registry/repository name" >&2
  exit 2
}
case "${PG_MAJOR}" in
  17 | 18) ;;
  *) echo "PG_MAJOR must be 17 or 18" >&2; exit 2 ;;
esac
[[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
  echo "TAG must use vX.Y.Z form" >&2
  exit 2
}
[[ "${SHA}" =~ ^[0-9a-f]{40}$ ]] || {
  echo "SHA must be a full lowercase 40-character commit SHA" >&2
  exit 2
}
[[ "${EXPECTED_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]] || {
  echo "EXPECTED_DIGEST must be a sha256 OCI manifest digest" >&2
  exit 2
}

VERSION="${TAG#v}"
SHORT_SHA="${SHA:0:12}"
SHA_SOURCE="${IMAGE}:pg${PG_MAJOR}-sha-${SHORT_SHA}"
PREPARED_SOURCE="${IMAGE}:pg${PG_MAJOR}-${TAG}-prepared"
TARGETS=(
  "${IMAGE}:pg${PG_MAJOR}-${TAG}"
  "${IMAGE}:pg${PG_MAJOR}-${VERSION}"
  "${IMAGE}:pg${PG_MAJOR}"
)
IMMUTABLE_TARGETS=(
  "${IMAGE}:pg${PG_MAJOR}-${TAG}"
  "${IMAGE}:pg${PG_MAJOR}-${VERSION}"
)
if [[ "${PG_MAJOR}" == 17 ]]; then
  TARGETS+=(
    "${IMAGE}:${TAG}"
    "${IMAGE}:${VERSION}"
    "${IMAGE}:latest"
  )
  IMMUTABLE_TARGETS+=(
    "${IMAGE}:${TAG}"
    "${IMAGE}:${VERSION}"
  )
fi
INSPECT_ATTEMPTS="${PGCONTEXT_PROMOTE_INSPECT_ATTEMPTS:-12}"
INSPECT_DELAY_SECONDS="${PGCONTEXT_PROMOTE_INSPECT_DELAY_SECONDS:-5}"
[[ "${INSPECT_ATTEMPTS}" =~ ^[1-9][0-9]*$ ]] || {
  echo "PGCONTEXT_PROMOTE_INSPECT_ATTEMPTS must be a positive integer" >&2
  exit 2
}
[[ "${INSPECT_DELAY_SECONDS}" =~ ^[0-9]+$ ]] || {
  echo "PGCONTEXT_PROMOTE_INSPECT_DELAY_SECONDS must be a non-negative integer" >&2
  exit 2
}

if [[ "${PLAN_ONLY}" -eq 1 ]]; then
  printf 'source_sha=%s\nsource_prepared=%s\nexpected_digest=%s\n' \
    "${SHA_SOURCE}" "${PREPARED_SOURCE}" "${EXPECTED_DIGEST}"
  printf 'target=%s\n' "${TARGETS[@]}"
  exit 0
fi

resolve_digest() {
  local reference="$1"
  local digest
  local output
  local attempt
  for ((attempt = 1; attempt <= INSPECT_ATTEMPTS; attempt++)); do
    if output="$(docker buildx imagetools inspect "${reference}" 2>&1)"; then
      digest="$(awk '/^Digest:/ { print $2; exit }' <<<"${output}")"
      if [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
        printf '%s\n' "${digest}"
        return 0
      fi
    fi
    if ((attempt < INSPECT_ATTEMPTS)); then
      sleep "${INSPECT_DELAY_SECONDS}"
    fi
  done
  echo "could not resolve an OCI manifest digest for ${reference}: ${output}" >&2
  return 1
}

resolve_optional_digest() {
  local reference="$1"
  local output
  local digest
  local attempt
  for ((attempt = 1; attempt <= INSPECT_ATTEMPTS; attempt++)); do
    if output="$(docker buildx imagetools inspect "${reference}" 2>&1)"; then
      digest="$(awk '/^Digest:/ { print $2; exit }' <<<"${output}")"
      [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]] || {
        echo "could not resolve an OCI manifest digest for ${reference}" >&2
        return 1
      }
      printf '%s\n' "${digest}"
      return 0
    fi
    case "${output}" in
      *"manifest unknown"* | *": not found"* | *"no such manifest"*) return 3 ;;
    esac
    if ((attempt < INSPECT_ATTEMPTS)); then
      sleep "${INSPECT_DELAY_SECONDS}"
    fi
  done
  echo "failed to inspect immutable tag ${reference}: ${output}" >&2
  return 1
}

for source in "${SHA_SOURCE}" "${PREPARED_SOURCE}"; do
  actual="$(resolve_digest "${source}")"
  [[ "${actual}" == "${EXPECTED_DIGEST}" ]] || {
    echo "${source} resolves to ${actual}, expected ${EXPECTED_DIGEST}" >&2
    exit 1
  }
done

for target in "${IMMUTABLE_TARGETS[@]}"; do
  if actual="$(resolve_optional_digest "${target}")"; then
    [[ "${actual}" == "${EXPECTED_DIGEST}" ]] || {
      echo "refusing to overwrite immutable tag ${target}: ${actual} != ${EXPECTED_DIGEST}" >&2
      exit 1
    }
  else
    result=$?
    [[ "${result}" -eq 3 ]] || exit "${result}"
  fi
done

tag_args=()
for target in "${TARGETS[@]}"; do
  tag_args+=(--tag "${target}")
done
docker buildx imagetools create "${tag_args[@]}" "${IMAGE}@${EXPECTED_DIGEST}"

for target in "${TARGETS[@]}"; do
  actual="$(resolve_digest "${target}")"
  [[ "${actual}" == "${EXPECTED_DIGEST}" ]] || {
    echo "promoted tag ${target} resolves to ${actual}, expected ${EXPECTED_DIGEST}" >&2
    exit 1
  }
done
