#!/bin/bash

# Script to:
## 1) create and checkout a new branch with the latest tag name
## 2) update NTH versions
## 3) commit release prep changes to new branch
## 4) create a PR from the new branch to upstream/main

set -euo pipefail

REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../; pwd -P )"
MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile
LATEST_VERSION=$(make -s -f $MAKEFILE_PATH latest-release-tag | cut -b 2- )
PREVIOUS_VERSION=$(make -s -f $MAKEFILE_PATH previous-release-tag | cut -b 2- )
MAJOR_INC=false
MINOR_INC=false
PATCH_INC=false
os=$(uname)

# files with versions, to update
REPO_README=$REPO_ROOT_PATH/README.md
CHART=$REPO_ROOT_PATH/config/helm/aws-node-termination-handler/Chart.yaml
CURR_CHART_VERSION=$(cat $CHART | grep 'version:' | xargs | cut -d' ' -f2 | tr -d '[:space:]')
UPDATED_CHART_VERSION=""
CHART_VALUES=$REPO_ROOT_PATH/config/helm/aws-node-termination-handler/values.yaml
# ReadMe will be updated post-release due to long NTH release time
FILES=("$CHART" "$CHART_VALUES")
FILES_CHANGED=()

# release prep
LATEST_TAG="v$LATEST_VERSION"
NEW_BRANCH="pr/$LATEST_TAG-release"
COMMIT_MESSAGE="🥑🤖 $LATEST_TAG release prep 🤖🥑"
RELEASE_PREP=true

# PR details
DEFAULT_REPO_FULL_NAME=$(make -s -f $MAKEFILE_PATH repo-full-name)
PR_BASE=main  # target
PR_TITLE="🥑🤖 $LATEST_TAG release prep"
PR_BODY="🥑🤖 Auto-generated PR for $LATEST_TAG release. Updating release versions in repo."
PR_LABEL_1="release-prep"
PR_LABEL_2="🤖 auto-generated🤖"
EC2_BOT_USER="ec2-bot 🤖"
EC2_BOT_EMAIL="ec2-bot@users.noreply.github.com"
EC2_BOT_SET=false

HELP=$(cat << 'EOM'
  Update repo with the new release version and create a pr from a new release prep branch.
  This script prompts the user with complete details about the PR before pushing the new local branch to remote and creating the PR.
  The new release version is the latest local git tag.
  Note: The local tag creation for a new release is separated from this script. A new tag must be created before this script is run which is automated when this script is run via make targets. 

  Usage: prepare-for-release [options]

  Options:
    -d      create a draft pr
    -m      update versions in ReadMe only
    -r      target repo full name for the pr (default: aws/aws-node-termination-handler)
    -h      help

  Examples:
    prepare-for-release -d                                          update release version in repo and create a draft pr against aws/aws-node-termination-handler
    prepare-for-release -r username/aws-node-termination-handler    update release version in repo and create a pr against username/aws-node-termination-handler
EOM
)

DRAFT=false
REPO_FULL_NAME=""
NEED_ROLLBACK=true

process_args() {
    while getopts "hdmr:" opt; do
        case ${opt} in
            h )
            echo -e "$HELP" 1>&2
            exit 0
            ;;
            d )
            DRAFT=true
            ;;
            m )
            FILES=("$REPO_README")
            # release should be completed, so no longer prep
            COMMIT_MESSAGE="🥑🤖 $LATEST_TAG release 🤖🥑"
            PR_TITLE="🥑🤖 $LATEST_TAG release"
            RELEASE_PREP=false
            ;;
            r )
            # todo: validate $REPO_FULL_NAME
            REPO_FULL_NAME="${OPTARG}"
            ;;
            \? )
            echo "$HELP" 1>&2
            exit 0
            ;;
        esac
    done

    # set repo full name to the default value if unset
    if [ -z $REPO_FULL_NAME ]; then
        REPO_FULL_NAME=$DEFAULT_REPO_FULL_NAME
    fi
}

# output formatting
export TERM="xterm"
RED=$(tput setaf 1)
MAGENTA=$(tput setaf 5)
RESET_FMT=$(tput sgr 0)
BOLD=$(tput bold)


determine_increment() {
  prev=$1
  updated=$2

  prev_major_v=$(echo $prev | tr '.' '\n' | head -1)
  prev_minor_v=$(echo $prev | tr '.' '\n' | head -2 | tail -1)
  prev_patch_v=$(echo $prev | tr '.' '\n' | tail -1)
  curr_major_v=$(echo $updated | tr '.' '\n' | head -1)
  curr_minor_v=$(echo $updated | tr '.' '\n' | head -2 | tail -1)
  curr_patch_v=$(echo $updated | tr '.' '\n' | tail -1)

  if [[ "$prev_major_v" -ne "$curr_major_v" ]]; then
    MAJOR_INC=true
  elif [[ "$prev_minor_v" -ne "$curr_minor_v" ]]; then
    MINOR_INC=true
  elif [[ "$prev_patch_v" -ne "$curr_patch_v" ]]; then
    PATCH_INC=true
  fi
}

increment_chart() {
  curr_chart_version=$1
  curr_major_v=$(echo $curr_chart_version | tr '.' '\n' | head -1)
  curr_minor_v=$(echo $curr_chart_version | tr '.' '\n' | head -2 | tail -1)
  curr_patch_v=$(echo $curr_chart_version | tr '.' '\n' | tail -1)

  if [[ $MAJOR_INC == true ]]; then
      new_major_v=$(echo $(($curr_major_v + 1)))
      UPDATED_CHART_VERSION=$(echo $new_major_v.0.0)
  elif [[ $MINOR_INC == true ]]; then
      new_minor_v=$(echo $(($curr_minor_v + 1)))
      UPDATED_CHART_VERSION=$(echo $curr_major_v.$new_minor_v.0)
  elif [[ $PATCH_INC == true ]]; then
      new_patch_v=$(echo $(($curr_patch_v + 1)))
      UPDATED_CHART_VERSION=$(echo $curr_major_v.$curr_minor_v.$new_patch_v)
  fi

  echo $UPDATED_CHART_VERSION
}

# verify origin tracking before creating and pushing new branches
verify_origin_tracking() {
    origin=$(git remote get-url origin 2>&1) || true

    if [[ $origin == "fatal: No such remote 'origin'" ]] || [[ $origin == "https://github.com/aws/aws-node-termination-handler.git" ]]; then
        echo -e "❌ ${RED}Expected remote 'origin' to be tracking fork but found \"$origin\". Set it up before running this script again.${RESET_FMT}"
        NEED_ROLLBACK=false
        exit 1
    fi
}

create_release_branch() {
    exists=$(git checkout -b $NEW_BRANCH 2>&1) || true

    if [[ $exists == "fatal: A branch named '$NEW_BRANCH' already exists." ]]; then
        echo -e "❌ ${RED}$exists${RESET_FMT}"
        NEED_ROLLBACK=false
        exit 1
    fi
    echo -e "✅ ${BOLD}Created new release branch $NEW_BRANCH\n\n${RESET_FMT}"
}

update_versions() {
    # update release version for release prep
    echo -e "🥑 Attempting to update NTH release version in preparation for a new release."
    UPDATED_CHART_VERSION=$(increment_chart $CURR_CHART_VERSION)

    for f in "${FILES[@]}"; do
        # do not exit if grep doesn't find $PREVIOUS_VERSION; continue to exit on all other exit codes
        has_incorrect_version=$(cat $f | grep $PREVIOUS_VERSION || [[ $? == 1 ]])
        chart_should_increment=$(cat $f | grep $CURR_CHART_VERSION || [[ $? == 1 ]])
        if [[ ! -z  $has_incorrect_version ]] || [[ ! -z  $chart_should_increment ]]; then
            if [[ "${os}" = "Linux" ]]; then
              sed -i "s/$PREVIOUS_VERSION/$LATEST_VERSION/g" $f
              sed -i "s/$CURR_CHART_VERSION/$UPDATED_CHART_VERSION/g" $f
            elif [[ "${os}" = "Darwin" ]]; then
              sed -i '' "s/$PREVIOUS_VERSION/$LATEST_VERSION/g" $f
              sed -i '' "s/$CURR_CHART_VERSION/$UPDATED_CHART_VERSION/g" $f
            else
              echo -e "❌ ${RED}Platform ${os} does not support NTH release. Please use: Linux or macOS ${RESET_FMT}"
              exit 1
            fi
            FILES_CHANGED+=("$f")
        fi
    done

    if [[ ${#FILES_CHANGED[@]} -eq 0 ]]; then
        echo -e "\nNo files were modified. Either all files already use git the latest release version $LATEST_VERSION or the files don't currently have the previous version $PREVIOUS_VERSION."
        exit 0
    else
        echo -e "✅✅ ${BOLD}Updated versions from $PREVIOUS_VERSION to $LATEST_VERSION in files: \n$(echo "${FILES_CHANGED[@]}" | tr ' ' '\n')"
        echo -e "To see changes, run \`git diff HEAD^ HEAD\`${RESET_FMT}"
    fi
    echo
}

commit_changes() {
    echo -e "\n🥑 Adding and committing release version changes."
    git config user.name "$EC2_BOT_USER"
    git config user.email "$EC2_BOT_EMAIL"
    EC2_BOT_SET=true
    git add "${FILES_CHANGED[@]}"
    git commit -m "$COMMIT_MESSAGE"
    echo -e "✅✅✅ ${BOLD}Committed release prep changes to new branch $NEW_BRANCH with commit message '$COMMIT_MESSAGE'\n\n${RESET_FMT}"
}

confirm_with_user_and_create_pr(){
    git checkout $NEW_BRANCH # checkout new branch before printing git diff

    echo -e "\n🥑${BOLD}The following PR will be created:\n"
    cat << EOM
    PR draft mode: $DRAFT
    PR target repository: $REPO_FULL_NAME
    PR source branch: $NEW_BRANCH
    PR target branch: $REPO_FULL_NAME/$PR_BASE
    PR title: $PR_TITLE
    PR body: $PR_BODY
    PR labels: $PR_LABEL_1, $PR_LABEL_2
    Changes in $NEW_BRANCH:
    ${MAGENTA}$(git diff HEAD^ HEAD)${RESET_FMT}
EOM

    # gh actions cannot respond to prompts
    if [[ $RELEASE_PREP == true ]]; then
      while true; do
          read -p "🥑${BOLD}Do you wish to create the release prep PR? Enter y/n  " yn
          case $yn in
              [Yy]* ) create_pr; break;;
              [Nn]* ) rollback; exit;;
              * ) echo "🥑Please answer yes or no.";;
          esac
      done
    else
      create_pr
    fi
    echo "${RESET_FMT}"
}

create_pr() {
    git push -u origin $NEW_BRANCH # sets source branch for PR to NEW_BRANCH on the fork or origin
    git checkout $NEW_BRANCH # checkout new branch before creating a pr

    if [[ $DRAFT == true ]]; then
        gh pr create \
            --repo "$REPO_FULL_NAME" \
            --base "$PR_BASE" \
            --title "$PR_TITLE" \
            --body "$PR_BODY" \
            --label "$PR_LABEL_1" --label "$PR_LABEL_2" \
            --draft
    else
        gh pr create \
            --repo "$REPO_FULL_NAME" \
            --base "$PR_BASE" \
            --title "$PR_TITLE" \
            --body "$PR_BODY" \
            --label "$PR_LABEL_1" --label "$PR_LABEL_2"
    fi

    if [[ $? == 0 ]]; then
        echo -e "✅✅✅✅ ${BOLD}Created $LATEST_TAG release prep PR\n${RESET_FMT}"
    else
        echo -e "❌ ${RED}PR creation failed.${RESET_FMT}❌"
        exit 1
    fi
}

# rollback partial changes to make this script atomic, iff the current execution of the script created a new branch and made changes
rollback() {
    if [[ $NEED_ROLLBACK == true ]]; then
        echo "🥑${BOLD}Rolling back"

        # checkout of current branch to main
        git checkout main

        # delete local and remote release branch only if current execution of the script created them
        git branch -D $NEW_BRANCH
        git push origin --delete $NEW_BRANCH

        # if multiple user.name are set, then only the latest(ec2-bot) will be removed
        if [[ $EC2_BOT_SET == true ]]; then
          echo "🥑${BOLD}Rolling back ec2-bot git config"
          git config --unset user.name
          git config --unset user.email
        fi

    fi
    echo "${RESET_FMT}"
}

handle_errors() {
    # error handling
    if [ $1 != "0" ]; then
        FAILED_COMMAND=${*:2}
        echo -e "\n❌ ${RED}Error occurred while running command '$FAILED_COMMAND'.${RESET_FMT}❌"
        rollback
        exit 1
    fi
    exit $1
}

sync_local_tags_from_remote() {
    # setup remote upstream tracking to fetch tags
    git remote add the-real-upstream https://github.com/aws/aws-node-termination-handler.git &> /dev/null || true
    git fetch the-real-upstream

    # delete all local tags
    git tag -l | xargs git tag -d

    # fetch remote tags
    git fetch the-real-upstream --tags

    # clean up tracking
    git remote remove the-real-upstream
}

main() {
    process_args "$@"
    trap 'handle_errors $? $BASH_COMMAND' EXIT

    verify_origin_tracking
    sync_local_tags_from_remote

    # if previous and latest version are equal, then the previous previous release versions may be present
    if [[ $PREVIOUS_VERSION == "$LATEST_VERSION" ]]; then
      PREVIOUS_VERSION=$(git tag -l --sort=-v:refname | sed -n '2 p' | cut -b 2-)
    fi

    determine_increment $PREVIOUS_VERSION $LATEST_VERSION

    echo -e "🥑 Attempting to create a release prep branch and PR with release version updates.\n   Previous version: $PREVIOUS_VERSION ---> Latest version: $LATEST_VERSION"
    create_release_branch
    update_versions
    commit_changes
    confirm_with_user_and_create_pr
}

main "$@"