#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

set -Eeuo pipefail

CURRENT_STEP=""

on_error() {
  local status=$?
  if [ -n "${CURRENT_STEP}" ]; then
    echo "FAILED: ${CURRENT_STEP}" >&2
  else
    echo "FAILED" >&2
  fi
  exit "${status}"
}

trap on_error ERR

usage() {
  cat <<USAGE
Usage:
  $0 <version> <rc> [options]

Arguments:
  <version>
      Release version in <major>.<minor>.<patch> format.
      Example: 0.9.1

  <rc>
      Numeric release candidate round.
      Example: 2 creates tag v0.9.1-rc.2 and dist dir apache-iceberg-rust-0.9.1-rc2.

Options:
  --release_ref <ref>
      Git commit-ish to archive and tag.
      Default: HEAD

  --dist_dir <dir>
      Directory where RC artifacts are written.
      Relative paths are resolved from the repository root.
      Default: dist

  --create_rc_tag <0|1>
      Whether to create the signed annotated RC git tag as the final release step.
      Default: 1

  --check_headers <0|1>
      Whether to check Apache license headers against the generated source archive.
      Default: 1

  --check_deps <0|1>
      Whether to run the dependency license check before creating artifacts.
      Default: 1

  --sign <0|1>
      Whether to create and verify the detached GPG signature for the source archive.
      Default: 1

  --upload_svn <0|1>
      Whether to upload RC artifacts to the ASF dev dist SVN repository.
      Default: 0

  --svn_dist_url <url>
      SVN directory URL where the RC artifact directory will be uploaded.
      Default: https://dist.apache.org/repos/dist/dev/iceberg

  -h, --help
      Show this help message.

Examples:
  $0 0.9.1 2
  $0 0.9.1 2 --create_rc_tag 0 --sign 0
  $0 0.9.1 2 --upload_svn 1
  $0 0.9.1 2 --release_ref abc123 --dist_dir /tmp/iceberg-rust-dist
USAGE
}

show_help_if_requested() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      -h | --help)
        usage
        exit 0
        ;;
    esac
    shift
  done
}

start_step() {
  CURRENT_STEP="$1"
  echo "==> ${CURRENT_STEP}"
}

finish_step() {
  echo "OK: ${CURRENT_STEP}"
  CURRENT_STEP=""
}

run_step() {
  local step="$1"
  shift
  start_step "${step}"
  "$@"
  finish_step
}

skip_step() {
  local step="$1"
  start_step "${step}"
  echo "OK: ${CURRENT_STEP} (skipped)"
  CURRENT_STEP=""
}

enabled() {
  [ "$1" != "0" ]
}

require_command() {
  local command_name="$1"
  if ! command -v "${command_name}" >/dev/null 2>&1; then
    echo "This step requires '${command_name}', but it is not installed." >&2
    return 1
  fi
}

require_checksum_command() {
  if ! command -v shasum >/dev/null 2>&1 && ! command -v sha512sum >/dev/null 2>&1; then
    echo "This step requires either 'shasum' or 'sha512sum'." >&2
    return 1
  fi
}

require_cargo_deny() {
  require_command cargo
  if ! cargo deny --version >/dev/null 2>&1; then
    echo "This step requires 'cargo-deny' for dependency license checks." >&2
    echo "Install it with: cargo install --locked cargo-deny" >&2
    echo "To skip this step locally, pass: --check_deps 0" >&2
    return 1
  fi
}

require_gpg_secret_key() {
  require_command gpg
  if ! gpg --list-secret-keys --with-colons 2>/dev/null | grep -q '^sec'; then
    echo "This step requires a GPG secret key for signing release artifacts or tags." >&2
    echo "Set up a release signing key first: https://rust.iceberg.apache.org/reference/setup_gpg.html" >&2
    echo "To skip signing locally, pass: --sign 0 --create_rc_tag 0" >&2
    return 1
  fi
}

require_option_value() {
  local option_name="$1"
  local option_value="${2:-}"
  if [ -z "${option_value}" ]; then
    echo "Missing value for ${option_name}." >&2
    usage >&2
    return 1
  fi
}

validate_bool_option() {
  local option_name="$1"
  local option_value="$2"
  case "${option_value}" in
    0 | 1) ;;
    *)
      echo "${option_name} must be 0 or 1, got '${option_value}'." >&2
      return 1
      ;;
  esac
}

parse_args() {
  local positional=()

  while [ "$#" -gt 0 ]; do
    case "$1" in
      -h | --help)
        usage
        exit 0
        ;;
      --release_ref | --release-ref)
        require_option_value "$1" "${2:-}"
        RELEASE_REF="$2"
        shift 2
        ;;
      --dist_dir | --dist-dir)
        require_option_value "$1" "${2:-}"
        RELEASE_DIST_DIR="$2"
        shift 2
        ;;
      --create_rc_tag | --create-rc-tag)
        require_option_value "$1" "${2:-}"
        validate_bool_option "$1" "$2"
        CREATE_RC_TAG="$2"
        shift 2
        ;;
      --check_headers | --check-headers)
        require_option_value "$1" "${2:-}"
        validate_bool_option "$1" "$2"
        CHECK_HEADERS="$2"
        shift 2
        ;;
      --check_deps | --check-deps)
        require_option_value "$1" "${2:-}"
        validate_bool_option "$1" "$2"
        CHECK_DEPS="$2"
        shift 2
        ;;
      --sign)
        require_option_value "$1" "${2:-}"
        validate_bool_option "$1" "$2"
        SIGN_ARCHIVE="$2"
        shift 2
        ;;
      --upload_svn | --upload-svn)
        require_option_value "$1" "${2:-}"
        validate_bool_option "$1" "$2"
        UPLOAD_SVN="$2"
        shift 2
        ;;
      --svn_dist_url | --svn-dist-url)
        require_option_value "$1" "${2:-}"
        SVN_DIST_URL="$2"
        shift 2
        ;;
      --*)
        echo "Unknown option: $1" >&2
        usage >&2
        return 1
        ;;
      *)
        positional+=("$1")
        shift
        ;;
    esac
  done

  if [ "${#positional[@]}" -ne 2 ]; then
    usage >&2
    return 1
  fi

  VERSION="${positional[0]}"
  RC="${positional[1]}"
}

validate_args() {
  if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Version '${VERSION}' must use format <major>.<minor>.<patch>." >&2
    return 1
  fi

  if [[ ! "${RC}" =~ ^[0-9]+$ ]]; then
    echo "RC '${RC}' must be a number, for example: 0, 1, or 2." >&2
    return 1
  fi
}

derive_release_names() {
  RC_TAG="v${VERSION}-rc.${RC}"
  RC_ID="apache-iceberg-rust-${VERSION}-rc${RC}"
  ARCHIVE_BASE_NAME="apache-iceberg-rust-${VERSION}"
  ARCHIVE_FILE_NAME="${ARCHIVE_BASE_NAME}.tar.gz"

  if [[ "${RELEASE_DIST_DIR}" = /* ]]; then
    DIST_ROOT="${RELEASE_DIST_DIR}"
  else
    DIST_ROOT="${REPO_ROOT}/${RELEASE_DIST_DIR}"
  fi
  RC_DIR="${DIST_ROOT}/${RC_ID}"
  SVN_DIST_URL="${SVN_DIST_URL%/}"
  SVN_RC_URL="${SVN_DIST_URL}/${RC_ID}"
}

check_release_ref() {
  require_command git
  git -C "${REPO_ROOT}" rev-parse --verify "${RELEASE_REF}^{commit}" >/dev/null
}

check_rc_tag_available() {
  require_command git
  if git -C "${REPO_ROOT}" rev-parse --verify --quiet "refs/tags/${RC_TAG}" >/dev/null; then
    echo "Tag '${RC_TAG}' already exists." >&2
    return 1
  fi
}

check_dependency_licenses() {
  require_cargo_deny
  (
    trap - ERR
    cd "${REPO_ROOT}"
    cargo deny check license
  )
}

prepare_output_directory() {
  rm -rf "${RC_DIR}"
  mkdir -p "${RC_DIR}"
}

create_source_archive() {
  require_command git
  git -C "${REPO_ROOT}" archive \
    --format=tar.gz \
    --output="${RC_DIR}/${ARCHIVE_FILE_NAME}" \
    --prefix="${ARCHIVE_BASE_NAME}/" \
    "${RELEASE_REF}"
}

check_license_headers() {
  require_command tar
  require_command docker
  require_command mktemp

  (
    trap - ERR
    local header_tmp_dir
    header_tmp_dir="$(mktemp -d -t "iceberg-rust-headers.XXXXXX")"
    cleanup() {
      rm -rf "${header_tmp_dir}"
    }
    trap cleanup EXIT

    tar -xzf "${RC_DIR}/${ARCHIVE_FILE_NAME}" -C "${header_tmp_dir}"
    docker run --rm -v "${header_tmp_dir}/${ARCHIVE_BASE_NAME}:/github/workspace" apache/skywalking-eyes header check
  )
}

sign_source_archive() {
  require_gpg_secret_key
  (
    trap - ERR
    cd "${RC_DIR}"
    gpg --armor --output "${ARCHIVE_FILE_NAME}.asc" --detach-sig "${ARCHIVE_FILE_NAME}"
  )
}

verify_source_signature() {
  require_command gpg
  (
    trap - ERR
    cd "${RC_DIR}"
    gpg --verify "${ARCHIVE_FILE_NAME}.asc" "${ARCHIVE_FILE_NAME}"
  )
}

generate_checksum() {
  require_checksum_command
  (
    trap - ERR
    cd "${RC_DIR}"
    if command -v shasum >/dev/null 2>&1; then
      shasum -a 512 "${ARCHIVE_FILE_NAME}" > "${ARCHIVE_FILE_NAME}.sha512"
    else
      sha512sum "${ARCHIVE_FILE_NAME}" > "${ARCHIVE_FILE_NAME}.sha512"
    fi
  )
}

verify_checksum() {
  require_checksum_command
  (
    trap - ERR
    cd "${RC_DIR}"
    if command -v shasum >/dev/null 2>&1; then
      shasum -a 512 -c "${ARCHIVE_FILE_NAME}.sha512"
    else
      sha512sum -c "${ARCHIVE_FILE_NAME}.sha512"
    fi
  )
}

require_release_artifact() {
  local artifact_path="$1"
  if [ ! -f "${artifact_path}" ]; then
    echo "Required release artifact is missing: ${artifact_path}" >&2
    return 1
  fi
}

upload_to_svn() {
  require_command svn
  require_release_artifact "${RC_DIR}/${ARCHIVE_FILE_NAME}"
  require_release_artifact "${RC_DIR}/${ARCHIVE_FILE_NAME}.asc"
  require_release_artifact "${RC_DIR}/${ARCHIVE_FILE_NAME}.sha512"

  if svn info "${SVN_RC_URL}" >/dev/null 2>&1; then
    echo "SVN target already exists: ${SVN_RC_URL}" >&2
    return 1
  fi

  svn import "${RC_DIR}" "${SVN_RC_URL}" -m "Prepare Apache Iceberg Rust ${VERSION} RC${RC}"
}

create_rc_tag() {
  require_command git
  require_gpg_secret_key
  git -C "${REPO_ROOT}" tag -s "${RC_TAG}" "${RELEASE_REF}" -m "Apache Iceberg Rust ${VERSION} RC${RC}"
}

print_summary() {
  echo "Created Apache Iceberg Rust RC artifacts:"
  echo "  Tag: ${RC_TAG}"
  echo "  Directory: ${RC_DIR}"
  echo "  Archive: ${RC_DIR}/${ARCHIVE_FILE_NAME}"
  if enabled "${SIGN_ARCHIVE}"; then
    echo "  Signature: ${RC_DIR}/${ARCHIVE_FILE_NAME}.asc"
  fi
  echo "  Checksum: ${RC_DIR}/${ARCHIVE_FILE_NAME}.sha512"
  if enabled "${UPLOAD_SVN}"; then
    echo "  SVN URL: ${SVN_RC_URL}"
  fi
}

print_vote_email() {
  cat <<MAIL
Draft vote email for dev@iceberg.apache.org:

---------------------------------------------------------
To: dev@iceberg.apache.org
Subject: [VOTE] Release Apache Iceberg Rust ${VERSION} RC${RC}

Hello Apache Iceberg Rust Community,

This is a call for a vote to release Apache Iceberg Rust version ${VERSION}.

The tag to be voted on is: ${RC_TAG}.

The release candidate:

https://dist.apache.org/repos/dist/dev/iceberg/${RC_ID}/

Keys to verify the release candidate:

https://downloads.apache.org/iceberg/KEYS

Git tag for the release:

https://github.com/apache/iceberg-rust/releases/tag/${RC_TAG}

Please download, verify, and test the release candidate.

This vote will be open for at least 72 hours and will remain open until the required number of votes is reached.

Please vote accordingly:
[ ] +1 Approve
[ ] +0 No opinion
[ ] -1 Disapprove (please provide a reason)

To learn more about Apache Iceberg, please visit:
https://rust.iceberg.apache.org/

Checklist for reference:
[ ] Download links are valid
[ ] Checksums and signatures are correct
[ ] LICENSE and NOTICE files are present
[ ] No unexpected binary files are included
[ ] All source files have ASF headers
[ ] The project builds successfully from source
[ ] pyiceberg-core builds and tests successfully

For more details, please refer to:
https://rust.iceberg.apache.org/release.html#how-to-verify-a-release

Thanks,
<your name>
---------------------------------------------------------
MAIL
}

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

VERSION=""
RC=""
RELEASE_REF="HEAD"
RELEASE_DIST_DIR="dist"
CREATE_RC_TAG="1"
CHECK_HEADERS="1"
CHECK_DEPS="1"
SIGN_ARCHIVE="1"
UPLOAD_SVN="0"
SVN_DIST_URL="https://dist.apache.org/repos/dist/dev/iceberg"

show_help_if_requested "$@"
run_step "Parse command arguments" parse_args "$@"
run_step "Validate release arguments" validate_args
derive_release_names

run_step "Check release reference ${RELEASE_REF}" check_release_ref

if enabled "${CREATE_RC_TAG}"; then
  run_step "Check RC tag ${RC_TAG} is available" check_rc_tag_available
else
  skip_step "Check RC tag ${RC_TAG} is available"
fi

if enabled "${CHECK_DEPS}"; then
  run_step "Check dependency licenses" check_dependency_licenses
else
  skip_step "Check dependency licenses"
fi

run_step "Prepare output directory ${RC_DIR}" prepare_output_directory
run_step "Create source archive ${ARCHIVE_FILE_NAME}" create_source_archive

if enabled "${CHECK_HEADERS}"; then
  run_step "Check source archive license headers" check_license_headers
else
  skip_step "Check source archive license headers"
fi

if enabled "${SIGN_ARCHIVE}"; then
  run_step "Sign source archive ${ARCHIVE_FILE_NAME}" sign_source_archive
  run_step "Verify source archive signature" verify_source_signature
else
  skip_step "Sign source archive ${ARCHIVE_FILE_NAME}"
  skip_step "Verify source archive signature"
fi

run_step "Generate SHA-512 checksum" generate_checksum
run_step "Verify SHA-512 checksum" verify_checksum

if enabled "${UPLOAD_SVN}"; then
  run_step "Upload artifacts to SVN dist ${SVN_RC_URL}" upload_to_svn
else
  skip_step "Upload artifacts to SVN dist ${SVN_RC_URL}"
fi

if enabled "${CREATE_RC_TAG}"; then
  run_step "Create signed RC tag ${RC_TAG}" create_rc_tag
else
  skip_step "Create signed RC tag ${RC_TAG}"
fi

run_step "Print artifact summary" print_summary
run_step "Print draft vote email" print_vote_email
