#!/usr/bin/env bash
# Prepare a release: validate inputs, bump pom.xml versions, commit, tag, push.
#
# This is the first script the orchestrator runs. After this completes, every
# subsequent component script syncs to origin/master to pick up the bump.
#
# Inputs (env vars): see docs/operations/release-principles.md §6.
# Args: --dry-run

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/_lib.sh"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run) DRY_RUN=true; shift ;;
    --execute) DRY_RUN=false; shift ;;
    -h|--help) echo "Usage: $0 [--dry-run|--execute]"; exit 0 ;;
    *) log_error "Unknown arg: $1"; exit 2 ;;
  esac
done

require_cmd git
require_cmd python3
require_release_inputs

log_step "Prepare release $RELEASE_VERSION (dry-run=$DRY_RUN)"

# Partial reruns (docker-only, post-maven) bump no pom version and create no
# tag, so the version/tag/changelog validation below does not apply to them —
# exit before it runs.
case "$RELEASE_TYPE" in
  docker-only|post-maven)
    log_info "Skipping prepare for RELEASE_TYPE=$RELEASE_TYPE (no pom bump or tag)"
    exit 0 ;;
esac

# Validation
if [[ "$RELEASE_VERSION" == "$OLD_VERSION" ]]; then
  log_error "RELEASE_VERSION must differ from OLD_VERSION (latest tag: $OLD_VERSION)"
  exit 1
fi
if [[ ! "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT$ ]]; then
  log_error "Current pom version must be X.Y.Z-SNAPSHOT, got: $CURRENT_VERSION"
  exit 1
fi
if [[ "$CREATE_VERSIONED_SITE" == "yes" && "${RELEASE_VERSION%.*}" == "${OLD_VERSION%.*}" ]]; then
  log_error "CREATE_VERSIONED_SITE=yes only valid for major or minor releases"
  exit 1
fi
if git -C "$REPO_ROOT" rev-parse "mockserver-$RELEASE_VERSION" >/dev/null 2>&1; then
  log_error "Tag mockserver-$RELEASE_VERSION already exists"
  exit 1
fi
if grep -Eq "^## \[$RELEASE_VERSION\]" "$REPO_ROOT/changelog.md"; then
  log_error "changelog.md already contains a section for $RELEASE_VERSION"
  exit 1
fi
UNRELEASED_SECTION=$(sed -n '/^## \[Unreleased\]/,/^## \[/p' "$REPO_ROOT/changelog.md" | sed '1d;$d')
if ! printf '%s\n' "$UNRELEASED_SECTION" | grep -Eq '^- '; then
  log_error "changelog.md has no bullets under Unreleased"
  exit 1
fi

log_info "Inputs OK:"
log_info "  Release:  $RELEASE_VERSION"
log_info "  Next:     $NEXT_VERSION"
log_info "  Previous: $OLD_VERSION"
log_info "  Current:  $CURRENT_VERSION"
log_info "  Type:     $RELEASE_TYPE"
log_info "  Versioned site: $CREATE_VERSIONED_SITE"

log_info "Updating pom.xml versions from $CURRENT_VERSION to $RELEASE_VERSION"
if is_dry_run; then
  log_dry "would: update Maven pom.xml files"
else
  update_pom_versions "$REPO_ROOT/mockserver" "$CURRENT_VERSION" "$RELEASE_VERSION"
fi

# Non-Maven version manifests. The npm/pypi/rubygems component scripts read
# the published version from per-package source files (package.json,
# pyproject.toml, version.rb) rather than $RELEASE_VERSION. If we don't bump
# them here, those publishers either republish-and-fail-403 (npm) or silently
# skip the publish because they think the version is already live (pypi,
# rubygems). The Helm chart's Chart.yaml is bumped by helm.sh itself.
NON_MAVEN_VERSION_PATHS=(
  mockserver-node/package.json
  mockserver-client-node/package.json
  mockserver-client-python/pyproject.toml
  mockserver-client-ruby/lib/mockserver/version.rb
  mockserver/mockserver-core/src/main/resources/org/mockserver/openapi/mock-server-openapi-embedded-model.yaml
)
log_info "Updating non-Maven version manifests to $RELEASE_VERSION"
if is_dry_run; then
  log_dry "would: bump ${NON_MAVEN_VERSION_PATHS[*]} to $RELEASE_VERSION"
else
  python3 - "$RELEASE_VERSION" "$REPO_ROOT" << 'PYEOF'
import re, sys, pathlib
new_v, repo_root = sys.argv[1], pathlib.Path(sys.argv[2])

def sub_once(path_rel, pattern, replacement, label):
    p = repo_root / path_rel
    text = p.read_text()
    new_text, n = re.subn(pattern, replacement, text, count=1)
    if n != 1:
        raise SystemExit(f"ERROR: could not find {label} in {path_rel}")
    p.write_text(new_text)
    print(f"  updated: {path_rel}")

# package.json — match the first top-level "version": "..." entry. Nested
# dependency entries are keyed by package name, not literal "version", so
# they won't false-match.
sub_once("mockserver-node/package.json",
         r'("version"\s*:\s*)"[^"]+"', f'\\g<1>"{new_v}"',
         '"version": "..."')
sub_once("mockserver-client-node/package.json",
         r'("version"\s*:\s*)"[^"]+"', f'\\g<1>"{new_v}"',
         '"version": "..."')

# pyproject.toml — line-anchored to avoid matching e.g. `requires-python`.
sub_once("mockserver-client-python/pyproject.toml",
         r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new_v}"',
         '^version = "..."')

# version.rb — Ruby uses single quotes here; mockserver-client.gemspec reads
# MockServer::VERSION from this file, so the gemspec needs no separate bump.
sub_once("mockserver-client-ruby/lib/mockserver/version.rb",
         r"VERSION\s*=\s*'[^']+'", f"VERSION = '{new_v}'",
         "VERSION = '...'")

# OpenAPI spec — the info.version field in the embedded YAML. Must be bumped
# in prepare (before the parallel publish group runs), not in finalize, so
# that swaggerhub.sh uploads a spec whose body matches its registry version
# label. Pattern is anchored to the indented `  version:` form to avoid
# false-matching other YAML keys.
sub_once("mockserver/mockserver-core/src/main/resources/org/mockserver/openapi/mock-server-openapi-embedded-model.yaml",
         r'(?m)^(\s+)version:\s*[0-9][^\s]*', f'\\g<1>version: {new_v}',
         "  version: X.Y.Z under info:")
PYEOF
fi

git_commit_and_push "release: set version $RELEASE_VERSION" \
  mockserver/ \
  "${NON_MAVEN_VERSION_PATHS[@]}"
git_tag_and_push "mockserver-$RELEASE_VERSION"

log_info "Prepare complete"
