#!/usr/bin/env bash

# Configure the script to exit when a command fails.
set -e

: ${SCRIPTS_ROOT:="$(dirname $0)"}
export SCRIPTS_ROOT
source "${SCRIPTS_ROOT:?}"/util.sh

# ===== CONSTANTS =====

: ${SELF:="$(basename $0)"}

: ${REPOSITORY_ROOT:="${SCRIPTS_ROOT:?}"/..}
VERSION_FILE="${REPOSITORY_ROOT:?}"/VERSION
CHANGELOG_FILE="${REPOSITORY_ROOT:?}"/CHANGELOG.md
OPENAPI_SPEC_FILE="${REPOSITORY_ROOT:?}"/docs/openapi/openapi.yaml
CARGO_TOML_FILE="${REPOSITORY_ROOT:?}"/Cargo.toml
CARGO_LOCK_FILE="${REPOSITORY_ROOT:?}"/Cargo.lock
LOCAL_RUN_CONSTANTS_FILE="${REPOSITORY_ROOT:?}"/local-run/scripts/constants.sh
LICENSE_FILE="${REPOSITORY_ROOT:?}"/prose.lic

VERSION="$(cat "${VERSION_FILE:?}")"

# ===== HELPER FUNCTIONS =====

description() {
	cat <<EOF
${Bold}Creates a new release for the Prose Pod API.${Bold_Off}

This script bumps the version number, then adds and pushes a tag to $(format_code origin).
EOF
}

usage() {
	cat <<EOF
$(format_title 'Usage:')
  $(format_command "${SELF:?}") $(format_arg 'major|minor|patch')

$(format_title 'Options:')
  $(format_subtitle 'Miscellaneous options:')
    $(format_flag --help)
      Explains what the command does and how to use it.
    $(format_flag --force)
      The script won't stop you if your index contains uncommitted changes.
EOF
}

help() {
	printf "$(description)\n"
	echo ''
	printf "$(usage)\n"
	exit 0
}

to-tag() {
	echo "v${1:?"Must pass a version number"}"
}

# A simplified `sed` command that works on both macOS and Linux.
replace() {
	local find="${1:?}"
	local replace="${2:?}"
	# NOTE: `//$'\n'/\\n` allows escaping newlines.
	local pattern="s@${find}@${replace//$'\n'/\\n}@gm" file="${3:?}"

	# Make `perl` exit with code 1 if no substitution was applied.
	pattern="$(printf '$changed ||= %s;\nEND { exit !$changed }' "${pattern}")"

	perl -i -pe "$pattern" "$file" || { error "Pattern $(format_code "$find") did not match anything in $(format_url "$file")."; return 1; }
}
replace-version() {
	replace "${1:?}" "\${1}${NEW_VERSION:?}\${2}" "${2:?}"
}

# ===== ARGUMENT PARSING =====

VERSION_COMPONENTS=($(echo "${VERSION:?}" | tr '.' ' '))

case "$1" in
	major)
		VERSION_COMPONENTS[0]=$(( VERSION_COMPONENTS[0] + 1 ))
		VERSION_COMPONENTS[1]=0
		VERSION_COMPONENTS[2]=0
		;;
	minor)
		VERSION_COMPONENTS[1]=$(( VERSION_COMPONENTS[1] + 1 ))
		VERSION_COMPONENTS[2]=0
		;;
	patch)
		VERSION_COMPONENTS[2]=$(( VERSION_COMPONENTS[2] + 1 ))
		;;
	--help) help ;;
	'') error "Expected at least one argument."; info "$(usage)"; die ;;
	*) error "Unknown argument: $(format_code $1)."; info "$(usage)"; die ;;
esac
# Skip first argument now that it's processed.
shift 1

for arg in "$@"; do
	case $arg in
		--force) FORCE=1 ;;
		--help) help ;;
		*) error "Unknown argument: $(format_code $arg)."; info "$(usage)"; die ;;
	esac
done

# ===== MAIN LOGIC =====

if [ "${DRY_RUN:-0}" -ne 0 ]; then
	die 'Dry run mode not supported.'
fi

# Ensure there are no uncommitted changes.
if [ -z "${FORCE-}" ]; then
	git diff-index --quiet HEAD || die "Your index contains uncommitted changes. Please commit or stash them before creating a release."
fi

# Ensure the changelog has been updated.
if [ -z "${FORCE-}" ] && git diff --quiet "$(to-tag "${VERSION:?}")" -- "${CHANGELOG_FILE:?}"; then
	die "Don’t forget to run $(format_code task changelog:prepare)."
fi

GIT_BRANCH="$(git branch --show-current)"

if [ -z "${NO_PULL-}" ]; then
	# Ensure linear history.
	info "Pulling $(format_code origin)…"
	git pull origin "${GIT_BRANCH:?}"
fi

# Convert the new version to a string.
NEW_VERSION=$(echo "${VERSION_COMPONENTS[*]}" | tr ' ' '.')

# Log some useful info.
info "Current version: $(to-tag "${VERSION:?}")"
info "New version: $(to-tag "${NEW_VERSION:?}")"
info "New commits:"
log_as_info_ git --no-pager log --reverse --no-merges \
  --format="- %C(auto)%h %s %C(green)(%ad)%C(reset)" --date=short --color \
  "$(to-tag "${VERSION:?}")"..HEAD

# Request the new license first, to abort with no change
# if the Cloud API doesn’t authorize the user.
info "Requesting a new Community license for $(to-tag "${NEW_VERSION:?}")…"
PROSE_CLOUD_API_SECRET="${PROSE_CLOUD_API_SECRET_COMMUNITY:-"${PROSE_CLOUD_API_SECRET:?}"}"
curl -sSf -X POST "${PROSE_CLOUD_API_URL:?}/v1/licenses/community" \
	-H 'Content-Type: application/json' \
	-d "{\"api_version\":\"${NEW_VERSION:?}\",\"secret\":\"${PROSE_CLOUD_API_SECRET:?"'PROSE_CLOUD_API_SECRET' is missing"}\"}" \
	--output "${LICENSE_FILE:?}"

# Update version numbers in files.
info "Changing version number in $(format_code "${VERSION_FILE:?}")…"
echo "${NEW_VERSION:?}" > "${VERSION_FILE:?}"
info "Changing version number in $(format_code "$(basename "${OPENAPI_SPEC_FILE:?}")")…"
replace-version '^(  version: ).+' "${OPENAPI_SPEC_FILE:?}"
info "Changing version number in $(format_code Cargo.toml)…"
replace-version '^(version = \").+(\")' "${CARGO_TOML_FILE:?}"
info "Changing version number in local run config…"
replace-version '(PROSE_POD_API_IMAGE_TAG:=).+(\})' "${LOCAL_RUN_CONSTANTS_FILE:?}"
info "Updating $(format_code Cargo.lock)…"
cargo check
info "Updating $(format_code CHANGELOG.md)…"
replace "compare/$(to-tag "${VERSION:?}")...HEAD" "$(cat <<EOF
compare/$(to-tag "${NEW_VERSION:?}")...HEAD

## [${NEW_VERSION:?}] - $(date -I)

[${NEW_VERSION:?}]: https://github.com/prose-im/prose-pod-api/compare/$(to-tag "${VERSION:?}")...$(to-tag "${NEW_VERSION:?}")
EOF
)" "${CHANGELOG_FILE:?}"

# Create & push a new git tag.
info "Committing changes…"
git add "${VERSION_FILE:?}" "${OPENAPI_SPEC_FILE:?}" "${CARGO_TOML_FILE:?}" "${LOCAL_RUN_CONSTANTS_FILE:?}" "${CARGO_LOCK_FILE:?}" "${CHANGELOG_FILE:?}" "${LICENSE_FILE:?}"
git commit -m "$(to-tag "${NEW_VERSION:?}")"
info "Creating tag…"
git tag "$(to-tag "${NEW_VERSION:?}")" -m "$(to-tag "${NEW_VERSION:?}")"
info "Pushing tag…"
git push ${FORCE_PUSH:+-f} --atomic origin "${GIT_BRANCH:?}" "$(to-tag "${NEW_VERSION:?}")"

success "Successfully created and pushed tag '$(to-tag "${NEW_VERSION:?}")'"
