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

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"

source "${SCRIPT_DIR}/common.sh"

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

usage() {
  cat <<EOF
Create prod release tags from the latest release branches for one or more services.

What this script does:
  1. Resolve each service release branch.
     - With --version, every service uses <service>/release-<major.minor>.
     - Without --version, each service uses its latest remote release branch.
  2. Resolve the next prod patch tag for that major.minor line.
     - If no remote tag exists for <service>/v<major.minor>.*, patch starts at 0.
     - Otherwise, patch is the existing max patch plus one.
  3. Check all target tags first and fail if any already exists.
  4. Print source branches, source SHAs, and target tags.
  5. With --push, push <service>/v<major.minor.patch> tags to origin,
     then create GitHub Releases with empty release notes.

Pushing those tags triggers the matching prod deploy workflow when that service
has a workflow listening to <service>/* tags. The GitHub Release title is the
tag name.

Usage:
  bash scripts/release/release-prod.sh --service <service>[,<service>...] [--version <major.minor>] [--push]

Examples:
  bash scripts/release/release-prod.sh --service ticket-manager
  bash scripts/release/release-prod.sh --service ticket-manager --service ticket-manager-web
  bash scripts/release/release-prod.sh --service ticket-manager,ticket-manager-web --version 0.1
  bash scripts/release/release-prod.sh --service ticket-manager --push

Options:
  --service, -s  Service slug. Can be repeated or comma-separated.
  --version, -v  Release branch version in major.minor format. Defaults to the latest release branch per service.
  --push         Push target tags to origin and create GitHub Releases. Without this, the script only prints the target tags.
  --help, -h     Show this help.

Supported services:
  $(supported_services_text)
EOF
}

validate_version() {
  local version="$1"
  [[ "${version}" =~ ^[0-9]+\.[0-9]+$ ]] || fail "version must be in major.minor format, for example 1.2"
}

validate_service() {
  local service="$1"
  [[ "${service}" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || fail "invalid service: ${service}"
  is_supported_service "${service}" || fail "unsupported service: ${service}. Supported services: $(supported_services_text)"
}

latest_release_version() {
  local service="$1"
  local output ref major minor major_num minor_num
  local max_major=-1
  local max_minor=-1

  if ! output="$(git ls-remote --heads origin "${service}/release-*")"; then
    fail "failed to query remote release branches for ${service}"
  fi
  while read -r _ ref; do
    [[ -n "${ref}" ]] || continue
    if [[ "${ref}" =~ ^refs/heads/${service}/release-([0-9]+)\.([0-9]+)$ ]]; then
      major="${BASH_REMATCH[1]}"
      minor="${BASH_REMATCH[2]}"
      major_num=$((10#${major}))
      minor_num=$((10#${minor}))
      if (( major_num > max_major || (major_num == max_major && minor_num > max_minor) )); then
        max_major="${major_num}"
        max_minor="${minor_num}"
      fi
    fi
  done <<< "${output}"

  if (( max_major < 0 )); then
    fail "no remote release branch found for ${service}"
  fi

  printf '%d.%d\n' "${max_major}" "${max_minor}"
}

release_branch_sha() {
  local branch="$1"
  local output sha ref

  if ! output="$(git ls-remote --heads origin "refs/heads/${branch}")"; then
    fail "failed to query remote release branch: ${branch}"
  fi
  while read -r sha ref; do
    if [[ "${ref}" == "refs/heads/${branch}" ]]; then
      printf '%s\n' "${sha}"
      return
    fi
  done <<< "${output}"

  fail "remote release branch does not exist: ${branch}"
}

next_patch_version() {
  local service="$1"
  local major="$2"
  local minor="$3"
  local output ref patch patch_num
  local max_patch=-1
  local tag_prefix="refs/tags/${service}/v${major}.${minor}."

  if ! output="$(git ls-remote --tags origin "${service}/v${major}.${minor}.*")"; then
    fail "failed to query remote prod tags for ${service}/v${major}.${minor}.*"
  fi
  while read -r _ ref; do
    [[ -n "${ref}" ]] || continue
    [[ "${ref}" == *'^{}' ]] && continue
    if [[ "${ref}" == "${tag_prefix}"* ]]; then
      patch="${ref#"${tag_prefix}"}"
      if [[ "${patch}" =~ ^[0-9]+$ ]]; then
        patch_num=$((10#${patch}))
        if (( patch_num > max_patch )); then
          max_patch="${patch_num}"
        fi
      fi
    fi
  done <<< "${output}"

  printf '%d\n' "$((max_patch + 1))"
}

remote_tag_exists() {
  local tag="$1"
  local output ref

  if ! output="$(git ls-remote --tags origin "refs/tags/${tag}")"; then
    fail "failed to query remote prod tag: ${tag}"
  fi
  while read -r _ ref; do
    if [[ "${ref}" == "refs/tags/${tag}" ]]; then
      return 0
    fi
  done <<< "${output}"

  return 1
}

services=()
version=""
push=false

while [[ $# -gt 0 ]]; do
  case "$1" in
    --service|-s)
      [[ $# -ge 2 ]] || fail "--service requires a value"
      IFS=',' read -ra service_values <<< "$2"
      services+=("${service_values[@]}")
      shift 2
      ;;
    --version|-v)
      [[ $# -ge 2 ]] || fail "--version requires a value"
      version="$2"
      shift 2
      ;;
    --push)
      push=true
      shift
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      usage
      fail "unknown argument: $1"
      ;;
  esac
done

[[ "${#services[@]}" -gt 0 ]] || fail "at least one --service is required"
if [[ -n "${version}" ]]; then
  validate_version "${version}"
fi

require_cmd git

declare -A selected_services=()
unique_services=()
for service in "${services[@]}"; do
  validate_service "${service}"
  if [[ -z "${selected_services[${service}]:-}" ]]; then
    selected_services["${service}"]=1
    unique_services+=("${service}")
  fi
done

cd "${REPO_ROOT}"

branches=()
source_shas=()
tags=()
for service in "${unique_services[@]}"; do
  release_version="${version}"
  if [[ -z "${release_version}" ]]; then
    log "resolving ${service} release version from remote ${service}/release-* branches"
    release_version="$(latest_release_version "${service}")"
  else
    log "resolving ${service} release version from --version: ${release_version}"
  fi
  log "resolved ${service} release version: ${release_version}"

  branch="${service}/release-${release_version}"
  source_sha="$(release_branch_sha "${branch}")"
  major="${release_version%%.*}"
  minor="${release_version#*.}"
  patch="$(next_patch_version "${service}" "${major}" "${minor}")"
  tag="${service}/v${major}.${minor}.${patch}"

  if remote_tag_exists "${tag}"; then
    fail "tag already exists: ${tag}"
  fi

  log "resolved ${service} prod tag: ${tag}"
  branches+=("${branch}")
  source_shas+=("${source_sha}")
  tags+=("${tag}")
done

if [[ "${push}" != true ]]; then
  log "target tags:"
  for i in "${!tags[@]}"; do
    log "  ${tags[${i}]} from ${branches[${i}]} (${source_shas[${i}]})"
  done
  log "dry run only; pass --push to create tags on origin and GitHub Releases"
  exit 0
fi

require_cmd gh
gh auth status --hostname github.com >/dev/null

for i in "${!tags[@]}"; do
  log "creating ${tags[${i}]} from ${branches[${i}]} (${source_shas[${i}]})"
  git push origin "${source_shas[${i}]}:refs/tags/${tags[${i}]}"
done

for tag in "${tags[@]}"; do
  log "creating GitHub Release for ${tag}"
  gh release create "${tag}" --title "${tag}" --notes "" --verify-tag
done
