#!/bin/bash

# Java Maven modules which create Docker images
JAVA_IMAGE_MODULES="server meta s2i"

# UI Maven modules wich create Docker images
UI_IMAGE_MODULES="ui-react"

# All modules which create images
ALL_IMAGE_MODULES="$JAVA_IMAGE_MODULES ui operator"

release::description() {
    echo "Perform a release"
}

release::usage() {
    cat - <<EOT
-n  --dry-run                 Dry run, which performs the whole build but does no tagging, artefact
                              upload or pushing Docker images
    --release-version <ver>   Version to release (e.g. "1.2.1"). One version arg is mandatory
    --snapshot-release        Snapshot release which can be created on a daily basis.
                              A timestamped version will be created automatically, and no Maven artefacts
                              are pushed to maven central. No moving tag will be moved, too.
    --settings <file>         Path to a custom settings.xml to use for the release.
                              This file must contain all the credentials to be used for Sonatype.
                              By default ~/.m2/settings.xml is used.
    --local-maven-repo <dir>  Local dir for holding the local Maven repo cache. If not given, then a new
                              temporary directory will be used (and removed after the release)
    --docker-user <user>      Docker user for Docker Hub
    --docker-password <pwd>   Docker password for Docker Hub
    --no-git-push             Don't push the release tag (and symbolic major.minor tag) at the end
    --git-remote              Name of the git remote to push to. If not given, its trying to be pushed
                              to the git remote to which the currently checked out branch is attached to.
                              Works only when on a branch, not when checked out directly.
    --log <log-file>          Write full log to <log-file>, only print progress to screen
    --skip-tests              Do not run tests
    --no-strict-checksums     Do not insist on strict checksum policy for downloaded Maven artifacts
-q  --quiet                   Adds quiet option to Maven options - only show errors
EOT
}

get_release_version() {

    if [ $(hasflag --snapshot-release) ]; then
        echo $(calc_timestamp_version "$topdir")
        return 1
    fi

    local release_version=$(readopt --release-version)
    if [ -z "${release_version}" ]; then
        echo "ERROR: Please specify --release-version"
        return 1
    fi
    echo $release_version
}

release::run() {
    source "$(basedir)/commands/util/maven_funcs"
    # source "$(basedir)/commands/util/operator_funcs"

    # Main application directory
    local topdir=$(appdir ".")

    # Validate release versions. Release versions have the foramt "1.3.4"
    local release_version=$(get_release_version)
    check_error $release_version

    # Get the Syndesis minor version (e.g. "1.3")
    local moving_tag=$(extract_minor_version $release_version)
    check_error $moving_tag

    # Write to logfile if requested
    if [ $(readopt --log) ]; then
        local logfile=$(readopt --log)
        touch $logfile
        tail -f $logfile > >(grep ^====) &
        local tail_pid=$!
        trap "kill $tail_pid" EXIT

        exec >>$logfile 2>&1
        sleep 1
    fi

    # Verify that there are no modified file in git repo
    check_git_clean "$topdir"

    # Temporary local repository to guarantee a clean build
    local local_maven_repo=$(readopt --local-maven-repo)
    if [ -z "$local_maven_repo" ]; then
        local_maven_repo=$(mktemp -d 2>/dev/null || mktemp -d -t 'maven_repo')
        trap "echo 'Removing temp maven repo $local_maven_repo' && rm -rf $local_maven_repo" "EXIT"
    fi

    # Calculate common maven options
    local maven_opts="$(extract_maven_opts $local_maven_repo)"

    # Set pom.xml version to the given release_version
    update_pom_versions "$topdir" "$release_version" "$maven_opts"

    # Build and stage artefacts to Sonatype
    build_and_stage_artefacts "$topdir" "$maven_opts"

    # Build all Docker Images
    docker_login
    create_syndesis_docker_images "$topdir" "$maven_opts"

    # Create the image for the upgrade
    create_upgrade_docker_image "$topdir" "$release_version"

    # Create the operator image binaries
    update_image_versions "$topdir" "$release_version" "$moving_tag"
    "$topdir/install/operator/build.sh" --operator-build docker --image-build docker --image-name "syndesis/syndesis-operator" --image-tag "$release_version"

    # For a test run, we are done
    if [ $(hasflag --dry-run -n) ]; then
        drop_staging_repo "$topdir" "$maven_opts"

        echo "==== Dry run finished, nothing has been committed"
        echo "==== Use 'git reset --hard' to cleanup"
        exit 0
    fi

    # ========================================================================
    # Commit, tag, release, push
    # --------------------------

    # Git Commit all changed files
    git_commit_files "$topdir" "$release_version"

    # Tag the release version
    git_tag_release "$release_version"

    # Create operator deploy YAMLs, image versions for the minor tags (without patchlevels)
    # and commit to git
    create_moving_tag_release "$topdir" "$release_version" "$moving_tag"

    # Pushing to Docker Hub
    docker_push "$release_version" "$moving_tag"

    # Release staging repo
    release_staging_repo "$topdir" "$maven_opts"

    # Prepare binaries for release
    prepare_binaries "${topdir}/install/operator/dist" "${topdir}/install/operator/releases"

    prerelease=false
    if [[ $(hasflag --snapshot-release) ]]; then
        prerelease=true
    fi

    # Push everything (if configured)
    git_push "$topdir" "$release_version" "$moving_tag"

    # Release the binaries
    publish_artifacts "${topdir}" "$release_version" $prerelease

    # Create release description based on commit between releases
    # if check_for_command gren; then
    #    gren release --data-source=commits --tags=$release_version --override
    # fi
}

create_moving_tag_release() {
    local topdir=$1
    local release_version=$2
    local moving_tag=$3

    if [ ! $(hasflag --snapshot-release) ]; then
        docker image tag "syndesis/syndesis-operator:$release_version" "syndesis/syndesis-operator:$moving_tag"
        docker push "syndesis/syndesis-operator:$moving_tag"

        echo "==== Git tag $moving_tag"
        git tag -f $moving_tag
    fi
}

# ===================================================================================================
# Prep actions:

calc_timestamp_version() {
    local topdir=$1
    local pom_version=$(grep -oPm2 "(?<=<version>)[^<]+" "$topdir/app/pom.xml"| tail -1| sed -e 's/\([0-9]*\.[0-9]*\).*/\1/')
    if [ -z "${pom_version}" ]; then
        echo "ERROR: Cannot extract version from app/pom.xml"
        return 1
    fi
    local patch_level=$(git tag | grep ^$pom_version | grep -v '-' | grep '[0-9]*\.[0-9]*\.' | sed -e s/${pom_version}.// | sort -n -r | head -1)
    if [ -z "${patch_level}" ]; then
      # without the patch level, i.e. for X.Y-SNAPSHOT in POMs we set
      # the patch level to -1 to have it evaluate to X.Y.0-YYYYMMDD
      # instead of X.Y.1-YYYYMMDD below
      patch_level=-1
    fi
    echo "${pom_version}.$((patch_level+1))-$(date '+%Y%m%d')"
}

check_git_clean() {
    local topdir=$1

    cd $topdir
    echo "==== Checking for clean Git Repo"
    set +e
    git diff-index --quiet HEAD --
    local git_uncommitted=$?
    set -e
    if [ $git_uncommitted != 0 ]; then
       echo "Untracked or changed files exist. Please run release on a clean repo"
       git status
       exit 1
    fi

    # we need to remove tags removed at origin so we can push tags that have been
    # removed, root cause is when we do `git push` an error is reported
    # `fatal: remote part of refspec is not a valid name in :...`
    git fetch --tags --prune
}

update_pom_versions() {
    local topdir="$1"
    local version="$2"
    local maven_opts="$3"

    cd $topdir/app
    echo "==== Updating pom.xml versions to $version"
    ./mvnw ${maven_opts} versions:set -DnewVersion=$version -DprocessAllModules=true -DgenerateBackupPoms=false

    # Update version in docs
    ./mvnw ${maven_opts} -f "$topdir/doc/pom.xml" versions:set -DnewVersion=$version -DprocessAllModules=true -DgenerateBackupPoms=false

    # Update version in integration tests
    cd $topdir/app/extension/maven-plugin/src/it
    for dir in $(ls -d *); do
      if [ -d $dir ]; then
        pushd $dir
        sed -i.bak -e "s/\(<syndesis\.version>\).*\(<\/syndesis\.version>\)/\\1$version\\2/"  pom.xml
        rm pom.xml.bak
        popd
      fi
    done
}

update_image_versions() {
    local topdir="$1"
    local version="$2"

    echo "==== Updating image versions to $version"
    for image in syndesis-ui syndesis-s2i syndesis-upgrade syndesis-meta syndesis-server; do
        sed -E "s|($image):latest|\1:$version|" -i $topdir/install/operator/build/conf/config.yaml
    done
}

check_github_username() {
    if [ -z "${GITHUB_USERNAME}" ]; then
        echo "ERROR: environment variable GITHUB_USERNAME has not been set."
        echo "Please populate it with your github id"
        return 1
    fi
}

check_github_access_token() {
    if [ -z "${GITHUB_ACCESS_TOKEN}" ]; then
        echo "ERROR: environment variable GITHUB_ACCESS_TOKEN has not been set."
        echo "Please populate it with a valid personal access token from github (with 'repo', 'admin:org_hook' and 'admin:repo_hook' scopes)."
        return 1
    fi
}

check_gren_access_token() {
    if [ -z "${GREN_GITHUB_TOKEN}" ]; then
        echo "ERROR: environment variable GREN_GITHUB_TOKEN has not been set."
        echo "Please populate it with a valid personal access token from github (with 'repo', 'admin:org_hook' and 'admin:repo_hook' scopes)."
        return 1
    fi
}

prepare_binaries() {
    local from=$1
    local to=$2

    if ! [[ -d ${from} ]]; then
        echo "ERROR: The directory where the binaries are located must be a valid directory, got [${from}]"
        return 1
    fi

    if ! [[ -d ${to} ]]; then
        mkdir ${to}
    fi

    for dist in darwin-amd64 linux-amd64 windows-amd64; do
        tar -zcf ${to}/syndesis-operator-${dist}.tar.gz -C ${from}/${dist} .
    done
}

publish_artifacts() {
    local top_dir=$1
    local tag=$2
    local prerelease=$3

    check_github_access_token
    check_github_username
    # check_gren_access_token

    local data="{\
        \"tag_name\": \"${tag}\", \
        \"name\": \"${tag}\", \
        \"target_commitish\": \"$(git rev-parse HEAD)\", \
        \"prerelease\": ${prerelease} \
    }"

    local remote=origin
    local github_api_url=$(git config --get remote.${remote:-origin}.url)
    # try to get URL to GitHub API server from remote should support both git and HTTP style remotes
    # e.g for:
    # git@github.com:syndesisio/syndesis.git
    # https://github.com/syndesisio/syndesis.git
    # result should be:
    # https://api.github.com/repos/syndesisio/syndesis
    github_api_url=${github_api_url/github.com?/api.github.com\/repos\/} # change from https://github.com to https://api.github.com, also account for `:` non-http git remote
    github_api_url=${github_api_url/%.git/} # remove .git at the end
    github_api_url=${github_api_url/*@/https:\//} # remove any username in ...@api.github.com

    if [ ${prerelease} == true ]; then
        # keep only last 10 snapshot releases
        local major_minor=${tag%.*} # this relies on having two dots in $tag, i.e. at least X.Y.Z
        if [ -z "${major_minor}" ]; then
            echo "ERROR: refusing to proceed MAJOR.MINOR version calculated as empty this would delete all releases"
            return 1
        fi
        local versions_to_discard=$(git for-each-ref --format="%(refname:short)" --sort=creatordate refs/tags/${major_minor}*|head -n -10)
        if [ ! -z "${versions_to_discard}" ]; then
            echo "About to remove the following tags: ${versions_to_discard}"
            for version in ${versions_to_discard}; do
                local release_url=$(curl -q -s -u ${GITHUB_USERNAME}:${GITHUB_ACCESS_TOKEN} "${github_api_url}/releases/tags/${version}" | jq -r .url)
                if [ "${release_url}" != "null" ]; then
                    curl -q -s -S --fail -X DELETE -u ${GITHUB_USERNAME}:${GITHUB_ACCESS_TOKEN} ${release_url}
                fi
            done
            git push --delete origin ${versions_to_discard}
        fi
    fi

    local upload_url=$(curl -q -s -S --fail \
      -X POST \
      -u ${GITHUB_USERNAME}:${GITHUB_ACCESS_TOKEN} \
      -H "Accept: application/vnd.github.v3+json" \
      -H "Content-Type: application/json" \
      -d "$data" \
      ${github_api_url}/releases | jq -r '.upload_url | sub("{.*"; "")'
    )

    if [[ ! $upload_url == http* ]]; then
        echo "ERROR: Cannot create release on remote github repository. Check if a release with the same tag already exists."
        return 1
    fi

    for file in $top_dir/install/operator/releases/*; do
        echo -n "Upload $file to $upload_url ..."
        curl -q -s -S --fail -X POST -u ${GITHUB_USERNAME}:${GITHUB_ACCESS_TOKEN} \
          -H "Accept: application/vnd.github.v3+json" \
          -H "Content-Type: application/tar+gzip" \
          --data-binary "@${file}" \
          ${upload_url}?name=${file##*/} >/dev/null 2>&1
          echo "done"
        local err=$?
        if [ $err -ne 0 ]; then
          echo "ERROR: Cannot upload release artifact $file on remote github repository"
          return 1
        fi
    done

    echo -n "Upload syndesis-cli.zip to $upload_url ..."
    (cd "$top_dir/tools/bin/" && zip -q -r - . | curl -q -s -S --fail -X POST -u ${GITHUB_USERNAME}:${GITHUB_ACCESS_TOKEN} \
      -H "Accept: application/vnd.github.v3+json" \
      -H "Content-Type: application/zip" \
      --data-binary @- \
      ${upload_url}?name=syndesis-cli.zip >/dev/null) 2>&1
      echo "done"
    local err=$?
    if [ $err -ne 0 ]; then
      echo "ERROR: Cannot upload release artifact syndesis-cli.zip on remote github repository"
      return 1
    fi
}

build_and_stage_artefacts() {
    local topdir="$1"
    local maven_opts="$2"

    cd $topdir/app

    if [ $(hasflag --snapshot-release) ]; then
        echo "==== Building locally (--no-maven-release)"
        ./mvnw ${maven_opts} install
    else
        echo "==== Building and staging Maven artefacts to Sonatype"
        ./mvnw ${maven_opts} -Prelease deploy -DstagingDescription="Staging Syndesis for $(readopt --release-version)"
    fi
}

docker_login() {
    if [ -n "$(readopt --docker-user)" ] && [ -n "$(readopt --docker-password)" ]; then
        echo "==== Login to Docker Hub"
        docker login -u "$(readopt --docker-user)" -p "$(readopt --docker-password)"
        trap "docker logout" "EXIT"
    fi
}

create_syndesis_docker_images() {
    local topdir=$1
    local maven_opts="$2"

    echo "==== Creating Docker images"
    cd $topdir/app
    for module in $JAVA_IMAGE_MODULES; do
        # -Pimage binds to fabric8:build
        ./mvnw ${maven_opts} -Prelease,image,flash -Dfabric8.mode=kubernetes -f $module package
    done
    ./mvnw ${maven_opts} -Prelease,image,flash -Dfabric8.mode=kubernetes -pl ${UI_IMAGE_MODULES// /,} fabric8:build
}

create_upgrade_docker_image() {
    local topdir=$1
    local release_version="$2"

    echo "==== Creating upgrade image syndesis/syndesis-upgrade:$release_version"
    cd $topdir/tools/upgrade

    # Copy over syndesis-cli jar
    cp $topdir/app/server/cli/target/syndesis-cli.jar .

    # Create the image
    docker build -t syndesis/syndesis-upgrade:${release_version} --build-arg version=${release_version} .
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Commit, push, release actions

docker_push() {
    local release_version=$1
    local moving_tag=$2

    echo "==== Pushing to Docker Hub"
    for module in $ALL_IMAGE_MODULES; do
        local image="syndesis/syndesis-$module"
        docker push "$image:$release_version"

        # The operator image needs to be recreated for the moving tag.
        # This is done in a later step.
        if [ $module != "operator" ]; then
            docker tag "$image:$release_version" "$image:$moving_tag"
            docker push "$image:$moving_tag"
        fi
    done

    # Push out upgrade image
    docker tag "syndesis/syndesis-upgrade:$release_version" "syndesis/syndesis-upgrade:$moving_tag"
    docker push "syndesis/syndesis-upgrade:$release_version"
    docker push "syndesis/syndesis-upgrade:$moving_tag"
}

release_staging_repo() {
    local topdir="$1"
    local maven_opts="$2"

    if [ $(hasflag --snapshot-release) ]; then
        return
    fi

    echo "==== Releasing Sonatype staging repo"
    cd $topdir/app
    ./mvnw ${maven_opts} -N -Prelease nexus-staging:release -DstagingDescription="Releasing $(readopt --release-version)"
}

git_commit_files() {
    local dir=$1
    local version=$2

    echo "==== Committing files to local git"
    cd $dir
    git_commit pom.xml "Update pom.xmls to $version"
}

git_tag_release() {
    local release_version=${1}

    echo "==== Tagging version $release_version"
    git tag -f "$release_version"
}

git_push() {
    local topdir=${1:-}
    local release_version=${2:-}
    local moving_tag=${3:-}

    cd $topdir

    if [ ! $(hasflag --no-git-push) ] && [ ! $(hasflag --dry-run -n) ]; then
        local remote=$(readopt --git-remote)
        if [ -z "${remote}" ]; then
            # Push to the remote attached to the local checkout branch
            remote=$(git for-each-ref --format='%(upstream:short)' $(git symbolic-ref -q HEAD) | sed -e 's/\([^\/]*\)\/.*/\1/')
            if [ -z "${remote}" ]; then
              echo "ERROR: Cannot find remote repository to git push to"
              exit 1
            fi
        fi

        echo "==== Pushing to GitHub"
        if [ -n "$release_version" ]; then
            echo "* Pushing $release_version"
            if [ $(hasflag --snapshot-release) ]; then
                # Force push to allow multiple releases per day
                git push -f -u $remote $release_version
            else
                git push -u $remote $release_version
            fi
        fi
        if [ ! $(hasflag --snapshot-release) ] && [ -n "$moving_tag" ]; then
            echo "* Pushing symbolic tag $moving_tag"
            git push -f -u $remote $moving_tag
        fi
    fi
}

# =======================================================================
# Side actions

drop_staging_repo() {
    local topdir="$1"
    local maven_opts="$2"

    if [ $(hasflag --snapshot-release) ]; then
        return
    fi

    echo "==== Dropping Sonatype staging repo"
    cd $topdir/app
    ./mvnw ${maven_opts} nexus-staging:drop -Prelease -DstagingDescription="Dropping repo"
}

# =======================================================================
# Helper

extract_maven_opts() {
    local maven_opts="-Dmaven.repo.local=$1 --batch-mode -V -e"

    if [ $(hasflag --quiet -q) ]; then
        maven_opts="$maven_opts -q"
    fi

    local settings_xml=$(readopt --settings-xml --settings)
    if [ -n "${settings_xml}" ]; then
        maven_opts="$maven_opts -s $settings_xml"
    fi

    if [ $(hasflag --skip-tests) ]; then
        maven_opts="$maven_opts -DskipTests -DskipITs"
    fi

    if [ ! $(hasflag --no-strict-checksums) ]; then
        maven_opts="$maven_opts -C"
    fi

    echo $maven_opts
}

git_commit() {
    local pattern="$1"
    local message="$2"

    local release_version=$(get_release_version)
    check_error $release_version

    if [ ! $(hasflag --dry-run -n) ]; then
        git ls-files --modified | grep $pattern | xargs git commit -m "[$release_version]: $message"
    fi
}

calc_dev_version() {
    local release_version=$1
    local minor_version=$(extract_minor_version $release_version)
    check_error $minor_version
    echo "${minor_version}-SNAPSHOT"
}

extract_minor_version() {
    local version=$1
    local minor_version=$(echo $version | sed 's/^\([0-9]*\.[0-9]*\)\.[0-9]*\(-.*\)*$/\1/')
    if [ "$minor_version" = "$version" ]; then
        echo "ERROR: Cannot extract minor version from ${version}, computed ${minor_version}"
        return 1
    fi
    echo $minor_version
}
