#!/bin/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 -e

GITBOX_URL=https://gitbox.apache.org/repos/asf/calcite-avatica-go.git
RELEASE_REPO=https://dist.apache.org/repos/dist/release/calcite/
DEV_REPO=https://dist.apache.org/repos/dist/dev/calcite/
PRODUCT=apache-calcite-avatica-go

function terminate() {
    printf "\n\nUser terminated build. Exiting...\n"
    exit 1
}

trap terminate SIGINT

init_release(){
    apk --no-cache add git gnupg tar
}

init_upload(){
    apk --no-cache add git subversion
}

make_directory_safe_for_git(){
   git config --global --add safe.directory /source
}

KEYS=()

GPG_COMMAND="gpg"

get_gpg_keys (){
    GPG_KEYS=$($GPG_COMMAND --list-keys --with-colons --keyid-format LONG)

    KEY_NUM=1

    KEY_DETAILS=""

    while read -r line; do

        IFS=':' read -ra PART <<< "$line"

        if [ ${PART[0]} == "pub" ]; then

            if [ -n "$KEY_DETAILS" ]; then
                KEYS[$KEY_NUM]=$KEY_DETAILS
                KEY_DETAILS=""
                ((KEY_NUM++))

            fi

            KEY_DETAILS=${PART[4]}
        fi

        if [ ${PART[0]} == "uid" ]; then
            KEY_DETAILS="$KEY_DETAILS - ${PART[9]}"
        fi

    done <<< "$GPG_KEYS"

    if [ -n "$KEY_DETAILS" ]; then
        KEYS[$KEY_NUM]=$KEY_DETAILS
    fi
}

mount_gpg_keys(){
    mkdir -p /.gnupg

    if [[ -z "$(ls -A /.gnupg)" ]]; then
        echo "Please mount the contents of your .gnupg folder into /.gnupg. Exiting..."
        exit 1
    fi

    mkdir -p /root/.gnupg

    cp -r /.gnupg/ /root/

    chmod -R 700 /root/.gnupg/

    rm -rf /root/.gnupg/*.lock
}

SELECTED_GPG_KEY=""

select_gpg_key(){

    get_gpg_keys

    export GPG_TTY=/dev/console

    touch /root/.gnupg/gpg-agent.conf
    echo 'default-cache-ttl 10000' >> /root/.gnupg/gpg-agent.conf
    echo 'max-cache-ttl 10000' >> /root/.gnupg/gpg-agent.conf

    echo "Starting GPG agent..."
    gpg-agent --daemon

    while $INVALID_KEY_SELECTED; do

        if [[ "${#KEYS[@]}" -le 0 ]]; then
            echo "You do not have any GPG keys available. Exiting..."
            exit 1
        fi

        echo "You have the following GPG keys:"

        for i in "${!KEYS[@]}"; do
                echo "$i) ${KEYS[$i]}"
        done

        read -p "Select your GPG key for signing: " KEY_INDEX

        SELECTED_GPG_KEY=$(sed 's/ -.*//' <<< ${KEYS[$KEY_INDEX]})

        if [[ -z $SELECTED_GPG_KEY ]]; then
            echo "Selected key is invalid, please try again."
            continue
        fi

        echo "Authenticating your GPG key..."

        echo "test" | $GPG_COMMAND --local-user $SELECTED_GPG_KEY --output /dev/null --sign -

        if [[ $? != 0 ]]; then
            echo "Invalid GPG passphrase or GPG error. Please try again."
            continue
        fi

        echo "You have selected the following GPG key to sign the release:"
        echo "${KEYS[$KEY_INDEX]}"

        INVALID_CONFIRMATION=true

        while $INVALID_CONFIRMATION; do
            read -p "Is this correct? (y/n) " CONFIRM

            if [[ ($CONFIRM == "Y") || ($CONFIRM == "y") ]]; then
                INVALID_KEY_SELECTED=false
                INVALID_CONFIRMATION=false
            elif [[ ($CONFIRM == "N") || ($CONFIRM == "n") ]]; then
                INVALID_CONFIRMATION=false
            fi
        done
    done
}

check_release_guidelines(){

    # Exclude files without the Apache license header
    missingHeaders=0

    for i in $(git ls-files); do
       case "$i" in
       # The following are excluded from the license header check

       # License files
       (LICENSE|NOTICE);;

       # Generated files
       (message/common.pb.go|message/requests.pb.go|message/responses.pb.go|Gopkg.lock|Gopkg.toml|go.mod|go.sum);;

       # Binaries
       (test-fixtures/calcite.png);;

       (*) if ! grep -q "Licensed to the Apache Software Foundation" $i; then
             echo "$i has no header" && ((missingHeaders=missingHeaders+1))
           fi;;
       esac
    done

    # Check copyright year in NOTICE
    if ! grep -Fq "Copyright 2012-$(date +%Y)" NOTICE; then
        echo "Ending copyright year in NOTICE is not $(date +%Y)"
        exit 1
    fi

    if [[ $missingHeaders -gt 0 ]]; then
        echo "Some files are missing the Apache license header"
        exit 1
    fi
}

check_if_tag_exists(){
    # Get new tags from remote
    git fetch --tags $GITBOX_URL

    for tag in "$@"
    do
        # Check to see if a tag with a v in front of it has been released
        if  git show-ref --tags | egrep -q "refs/tags/v$tag$"; then
            echo "A release with version $1 was already released. Check that the version number entered is correct."
            exit 1
        fi

        # Check to see if a tag without a v in front of it has been released (3.0.0 and below)
        if  git show-ref --tags | egrep -q "refs/tags/$tag$"; then
            echo "A release with version $1 was already released. Check that the version number entered is correct."
            exit 1
        fi
    done
}

check_local_remote_are_even(){
    REMOTE_COMMIT=$(git ls-remote $GITBOX_URL | head -1 | sed "s/[[:space:]]HEAD//")
    LOCAL_COMMIT=$(git rev-parse main)

    if [[ $REMOTE_COMMIT != $LOCAL_COMMIT ]]; then
        echo "Main branch in Apache repository is not even with local branch main"
        exit 1
    fi
}


check_import_paths(){
    TAG_MAJOR_VERSION=$(echo $1 | sed -e 's/\..*//')

    # Check that go.mod's module path contains the right version
    if ! grep -Fq "module github.com/apache/calcite-avatica-go/$TAG_MAJOR_VERSION" go.mod; then
        echo "Module declaration in go.mod does not contain the correct version. Expected: $TAG_MAJOR_VERSION"
        exit 1
    fi

    # Make sure import paths contain the correct version
    BAD_IMPORT_PATHS=false

    for i in $(git ls-files); do

        if [[ "$i" == "docker.sh" || "$i" =~ \.md$ ]]; then
            continue
        fi

        lines=$(grep -F -s '"github.com/apache/calcite-avatica-go' $i) || true

        if ! [[ -z "$lines" ]]; then
            while read -r line; do
                if ! grep -q "github.com/apache/calcite-avatica-go/$TAG_MAJOR_VERSION" <<< "$line" ; then
                    BAD_IMPORT_PATHS=true
                    echo "Import for github.com/apache/calcite-avatica-go in $i does not have the correct version ($TAG_MAJOR_VERSION) in its path"
                fi
            done <<< "$lines"
        fi
    done

    if "$BAD_IMPORT_PATHS" == true; then
        exit 1
    fi
}

RELEASE_VERSION=""
RC_NUMBER=""
ASF_USERNAME=""
ASF_PASSWORD=""

set_git_credentials(){
    echo https://$ASF_USERNAME:$ASF_PASSWORD@gitbox.apache.org >> /root/.git-credentials
    git config --global credential.helper 'store --file=/root/.git-credentials'
}

get_asf_credentials(){
    while $ASF_CREDS_NOT_CONFIRMED; do
        read -p "Enter your ASF username: " ASF_USERNAME
        read -s -p "Enter your ASF password: " ASF_PASSWORD
        printf "\n"
        echo "Your ASF Username is:" $ASF_USERNAME

        INVALID_CONFIRMATION=true

        while $INVALID_CONFIRMATION; do
            read -p "Is this correct? (y/n) " CONFIRM

            if [[ ($CONFIRM == "Y") || ($CONFIRM == "y") ]]; then
                ASF_CREDS_NOT_CONFIRMED=false
                INVALID_CONFIRMATION=false
            elif [[ ($CONFIRM == "N") || ($CONFIRM == "n") ]]; then
                INVALID_CONFIRMATION=false
            fi
        done
    done
}

clean_release_directory(){
    rm -rf dist
}

make_release_artifacts(){

    CURRENT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2)

    if [ $CURRENT_BRANCH != "main" ]; then
        echo "You are currently on the $CURRENT_BRANCH branch. A release must be made from the main branch. Exiting..."
        exit 1
    fi

    check_local_remote_are_even

    check_release_guidelines

    while $NOT_CONFIRMED; do
        read -p "Enter the version number to be released (example: 4.0.0, do not include the rc number): " RELEASE_VERSION
        read -p "Enter the release candidate number (example: if you are releasing rc0, enter 0): " RC_NUMBER
        echo "Build configured as follows:"
        echo "Release: $RELEASE_VERSION-rc$RC_NUMBER"

        INVALID_CONFIRMATION=true

        while $INVALID_CONFIRMATION; do
            read -p "Is this correct? (y/n) " CONFIRM

            if [[ ($CONFIRM == "Y") || ($CONFIRM == "y") ]]; then
                NOT_CONFIRMED=false
                INVALID_CONFIRMATION=false
            elif [[ ($CONFIRM == "N") || ($CONFIRM == "n") ]]; then
                INVALID_CONFIRMATION=false
            fi
        done
    done

    select_gpg_key

    check_if_tag_exists $RELEASE_VERSION $RELEASE_VERSION-rc$RC_NUMBER

    TAG="v$RELEASE_VERSION-rc$RC_NUMBER"

    check_import_paths $TAG

    clean_release_directory

    TAR_FILE=$PRODUCT-$RELEASE_VERSION-src.tar.gz
    RELEASE_DIR=dist/$PRODUCT-$RELEASE_VERSION-rc$RC_NUMBER

    #Make release dir
    mkdir -p $RELEASE_DIR

    # Make tar
    tar -zcf $RELEASE_DIR/$TAR_FILE --transform "s/^/$PRODUCT-$RELEASE_VERSION-src\//g" $(git ls-files)

    cd $RELEASE_DIR

    # Calculate SHA512
    gpg --print-md SHA512 $TAR_FILE > $TAR_FILE.sha512

    # Sign
    gpg -u $SELECTED_GPG_KEY --armor --output $TAR_FILE.asc --detach-sig $TAR_FILE

    echo "Release artifacts created!"
}

make_release_artifacts_and_push_tag(){
    make_release_artifacts

    get_asf_credentials

    # Create the tag
    git tag $TAG

    # Push the tag
    set_git_credentials

    git push $GITBOX_URL $TAG

    echo "Release $RELEASE_VERSION-rc$RC_NUMBER has been tagged and pushed!"
}

publish_release_for_voting(){

    LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)

    if [[ ! $LATEST_TAG =~ .+-rc[[:digit:]]+$ ]]; then
        echo "The latest tag ($LATEST_TAG) is not a RC release and should not be published for voting."
        exit 1
    fi

    TAG_WITHOUT_V=$(echo $LATEST_TAG | sed -e 's/v//')
    TAG_WITHOUT_RC=$(echo $TAG_WITHOUT_V | sed -e 's/-rc[0-9][0-9]*//')
    SOURCE_RELEASE=$PRODUCT-$TAG_WITHOUT_RC-src.tar.gz
    GPG_SIGNATURE=$PRODUCT-$TAG_WITHOUT_RC-src.tar.gz.asc
    SHA512=$PRODUCT-$TAG_WITHOUT_RC-src.tar.gz.sha512
    COMMIT=$(git rev-list -n 1 $LATEST_TAG)

    # Check to see a release is built
    MISSING_FILES=false

    if [ ! -f "dist/$PRODUCT-$TAG_WITHOUT_V/$SOURCE_RELEASE" ]; then
        echo "Did not find source release ($SOURCE_RELEASE) in dist folder."
        MISSING_FILES=true
    fi

    if [ ! -f "dist/$PRODUCT-$TAG_WITHOUT_V/$GPG_SIGNATURE" ]; then
        echo "Did not find GPG signature ($GPG_SIGNATURE) in dist folder."
        MISSING_FILES=true
    fi

    if [ ! -f "dist/$PRODUCT-$TAG_WITHOUT_V/$SHA512" ]; then
        echo "Did not find SHA512 ($SHA512) in dist folder."
        MISSING_FILES=true
    fi

    if $MISSING_FILES == true; then
        exit 1
    fi

    while $NOT_CONFIRMED; do
        echo "Publish configured as follows:"
        echo "Release: $TAG_WITHOUT_V"

        INVALID_CONFIRMATION=true

        while $INVALID_CONFIRMATION; do
            read -p "Is this correct? (y/n) " CONFIRM

            if [[ ($CONFIRM == "Y") || ($CONFIRM == "y") ]]; then
                NOT_CONFIRMED=false
                INVALID_CONFIRMATION=false
            elif [[ ($CONFIRM == "N") || ($CONFIRM == "n") ]]; then
                INVALID_CONFIRMATION=false
            fi
        done
    done

    HASH_CONTENTS=$(cat "dist/$PRODUCT-$TAG_WITHOUT_V/$SHA512" | tr -d '\n')
    HASH=${HASH_CONTENTS#"$SOURCE_RELEASE: "}

    get_asf_credentials

    svn checkout $DEV_REPO /tmp/calcite --depth empty
    cp -R dist/$PRODUCT-$TAG_WITHOUT_V /tmp/calcite/

    cd /tmp/calcite
    svn add $PRODUCT-$TAG_WITHOUT_V
    chmod -x $PRODUCT-$TAG_WITHOUT_V/*

    svn commit -m "$PRODUCT-$TAG_WITHOUT_V" --force-log --username $ASF_USERNAME --password $ASF_PASSWORD

    [[ $LATEST_TAG =~ -rc([[:digit:]]+)$ ]]
    RC_NUMBER=${BASH_REMATCH[1]}

    read -p "Please enter your first name for the voting email: " FIRST_NAME

    echo "The release $PRODUCT-$TAG_WITHOUT_V has been uploaded to the development repository."
    printf "\n"
    printf "\n"
    echo "Email the following message to dev@calcite.apache.org. Please check the message before sending."
    printf "\n"
    echo "To: dev@calcite.apache.org"
    echo "Subject: [VOTE] Release $PRODUCT-$TAG_WITHOUT_RC (release candidate $RC_NUMBER)"
    echo "Message:
Hi all,

I have created a release for Apache Calcite Avatica Go $TAG_WITHOUT_RC, release candidate $RC_NUMBER.

Thanks to everyone who has contributed to this release. The release notes are available here:
https://github.com/apache/calcite-avatica-go/blob/$LATEST_TAG/site/_docs/go_history.md

The commit to be voted on:
https://gitbox.apache.org/repos/asf?p=calcite-avatica-go.git;a=commit;h=$COMMIT

The hash is $COMMIT

The artifacts to be voted on are located here:
$DEV_REPO$PRODUCT-$TAG_WITHOUT_V/

The hashes of the artifacts are as follows:
src.tar.gz $HASH

Release artifacts are signed with the following key:
https://people.apache.org/keys/committer/$ASF_USERNAME.asc

Instructions for running the test suite is located here:
https://github.com/apache/calcite-avatica-go/blob/$LATEST_TAG/site/develop/avatica-go.md#testing

Please vote on releasing this package as Apache Calcite Avatica Go $TAG_WITHOUT_RC.

To run the tests without a Go environment, install docker and docker compose. Then, in the root of the release's directory, run: docker compose run test

When the test suite completes, run \"docker compose down\" to remove and shutdown all the containers.

The vote is open for the next 72 hours and passes if a majority of at least three +1 PMC votes are cast.

[ ] +1 Release this package as Apache Calcite Avatica Go $TAG_WITHOUT_RC
[ ]  0 I don't feel strongly about it, but I'm okay with the release
[ ] -1 Do not release this package because...


Here is my vote:

+1 (binding)

$FIRST_NAME
"
}

promote_release(){
    LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)

    if [[ ! $LATEST_TAG =~ .+-rc[[:digit:]]+$ ]]; then
        echo "The latest tag ($LATEST_TAG) is not a RC release and should not be re-released."
        exit 1
    fi

    TAG_WITHOUT_V=$(echo $LATEST_TAG | sed -e 's/v//')
    TAG_WITHOUT_RC=$(echo $TAG_WITHOUT_V | sed -e 's/-rc[0-9][0-9]*//')

    if ! svn ls $DEV_REPO/$PRODUCT-$TAG_WITHOUT_V; then
        echo "The release $PRODUCT-$TAG_WITHOUT_V was not found in the dev repository. Was it uploaded for voting?"
        exit 1
    fi

    get_asf_credentials

    set_git_credentials

    git tag v$TAG_WITHOUT_RC $LATEST_TAG
    git tag rel/v$TAG_WITHOUT_RC $LATEST_TAG

    git push $GITBOX_URL v$TAG_WITHOUT_RC
    git push $GITBOX_URL rel/v$TAG_WITHOUT_RC

    svn checkout $RELEASE_REPO /tmp/release
    rm -rf /tmp/release/$PRODUCT-$TAG_WITHOUT_RC
    mkdir -p /tmp/release/$PRODUCT-$TAG_WITHOUT_RC

    svn checkout $DEV_REPO/$PRODUCT-$TAG_WITHOUT_V /tmp/rc
    cp -rp /tmp/rc/* /tmp/release/$PRODUCT-$TAG_WITHOUT_RC

    cd /tmp/release

    svn add $PRODUCT-$TAG_WITHOUT_RC

    # If there is more than 1 release, delete all of them, except for the newest one
    # To do this, we do the following:
    # 1. Get the list of releases with verbose information from svn
    # 2. Sort by the first field (revision number) in descending order
    # 3. Select apache-calcite-avatica-go releases
    # 4. Exclude the release we're trying to promote, in case it was from a failed promotion.
    # 5. Trim all whitespace down to 1 empty space.
    # 6. Select field 7, which is each release's folder
    CURRENT_RELEASES=$(svn ls -v $RELEASE_REPO | sort -k1 -r | grep $PRODUCT | grep -v $PRODUCT-$TAG_WITHOUT_RC | tr -s ' ' | cut -d ' ' -f 7)

    RELEASE_COUNT=0
    while read -r RELEASE; do
        if [[ $RELEASE_COUNT -ne 0 ]]; then
            svn rm $RELEASE
            echo "Removing release $RELEASE"
        fi

        RELEASE_COUNT=$((RELEASE_COUNT+1))
    done <<< "$CURRENT_RELEASES"

    svn commit -m "Release $PRODUCT-$TAG_WITHOUT_RC" --force-log --username $ASF_USERNAME --password $ASF_PASSWORD

    echo "Release $PRODUCT-$LATEST_TAG successfully promoted to $PRODUCT-$TAG_WITHOUT_RC"
}

compile_protobuf(){

    if [ -z "$AVATICA_VERSION" ]; then
        echo "The AVATICA_VERSION environment variable must be set to a valid avatica version"
    fi

    if [ -z "$PROTOBUF_VERSION" ]; then
        echo "The PROTOBUF_VERSION environment variable must be set to a valid protobuf version"
    fi

    apt update && apt install unzip

    # Install go protobuf compiler
    go install google.golang.org/protobuf/cmd/protoc-gen-go

    # Install protoc
    mkdir -p /tmp/protoc
    wget -q -O /tmp/protoc/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip
    cd /tmp/protoc
    unzip protoc.zip
    mv /tmp/protoc/include /usr/local/include
    mv /tmp/protoc/bin/protoc /usr/local/bin/protoc

    # Clone a temp copy of avatica and check out the selected version
    mkdir /tmp/avatica
    cd /tmp/avatica
    git init
    git remote add origin https://github.com/apache/calcite-avatica.git
    git config core.sparsecheckout true
    echo "core/src/main/protobuf/*" >> .git/info/sparse-checkout

    git fetch --depth=1 origin rel/avatica-$AVATICA_VERSION
    git checkout FETCH_HEAD

    # Compile the protobuf
    GITHUB_PACKAGE=github.com/apache/calcite-avatica-go
    protoc --proto_path=/tmp/avatica/core/src/main/protobuf --go_out=/source --go_opt=module=$GITHUB_PACKAGE --go_opt=Mcommon.proto=$GITHUB_PACKAGE/message --go_opt=Mrequests.proto=$GITHUB_PACKAGE/message --go_opt=Mresponses.proto=$GITHUB_PACKAGE/message /tmp/avatica/core/src/main/protobuf/*.proto --go_opt=default_api_level=API_OPAQUE #Remove API_OPAQUE when Avatica protobufs are updated to edition 2024

    echo "Protobuf compiled successfully"
}

case $1 in
    dry-run)
        init_release
        make_directory_safe_for_git
        mount_gpg_keys
        make_release_artifacts
        ;;

    release)
        init_release
        make_directory_safe_for_git
        mount_gpg_keys
        make_release_artifacts_and_push_tag
        ;;

    clean)
        clean_release_directory
        echo "Release directory ./dist removed"
        ;;

    publish-release-for-voting)
        init_upload
        make_directory_safe_for_git
        publish_release_for_voting
        ;;

    promote-release)
        init_upload
        make_directory_safe_for_git
        promote_release
        ;;

    compile-protobuf)
        compile_protobuf
        ;;

    *)
       echo $"Usage: $0 {dry-run|release|clean|publish-release-for-voting|promote-release}"
       ;;

esac

# End