#!/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 upgrade"

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
    --github-user <user>      User for GitHub, used to release and publish artifacts
    --github-token <token>    Token with (full) 'repo' permission
    --quayio-user <user>      User for Quay.io
    --quayio-password <pwd>   Password for Quay.io
    --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.
    --gpg-keyname             Name of the GPG key to sign with (USER-ID)
    --gpg-passphrase          Passphrase used to unlock the GPG key
    --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

    if [[ $(hasflag --snapshot-release) ]]; then
      moving_tag+='-prerelease'
    fi

    # 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"

    # Calculate common maven options
    local maven_opts
    maven_opts="$(extract_maven_opts "${topdir}")"

    # 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
    image_registry_login
    create_syndesis_container_images "$topdir" "$maven_opts"

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

    # Create the operator image binaries
    update_image_versions "$topdir" "$release_version"
    "$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 image registry
    push_container_images "$release_version" "$moving_tag"
    if [ "$(git branch --show-current)" == "$(github_default_branch)" ]; then
        push_container_images "$release_version" latest
    fi

    # 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
        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
}

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 --owner=0 --group=0 -C ${from}/${dist} .
    done
}

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

    local github_username
    github_username="$(readopt --github-user)"

    local github_token
    github_token="$(readopt --github-token)"

    if [ -z "${github_username}" ] || [ -z "${github_token}" ]; then
      echo "ERROR: Missing --github-user and --github-token parameters"
      return 1
    fi

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

    local remote=origin
    local github_api_url
    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
        versions_to_discard=$(git for-each-ref --format="%(refname:short)" --sort=creatordate refs/tags/"${major_minor}"*|head -n -10)
        if [ -n "${versions_to_discard}" ]; then
            echo "About to remove the following tags: ${versions_to_discard}"
            for version in ${versions_to_discard}; do
                local release_url
                release_url=$(curl -q -s -u "${github_username}:${github_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_token}" "${release_url}"
                fi
            done
            git push --delete origin ${versions_to_discard}
        fi
    fi

    local upload_url
    # create or update a GitHub release, the daily builds will create a release
    # while a proper release will be initiaded by creating a GitHub release first
    upload_url=$(curl -q -s -S --fail \
      -X POST \
      -u "${github_username}:${github_token}" \
      -H "Accept: application/vnd.github.v3+json" \
      -H "Content-Type: application/json" \
      -d "$data" \
      "${github_api_url}/releases" 2> /dev/null | jq -r '.upload_url | sub("{.*"; "")' \
    || curl -q -s -S --fail \
      -X PATCH \
      -u "${github_username}:${github_token}" \
      -H "Accept: application/vnd.github.v3+json" \
      -H "Content-Type: application/json" \
      -d "$data" \
      "$(curl -q -s -S --fail \
         -u "${github_username}:${github_token}" \
         -H "Accept: application/vnd.github.v3+json" \
         "${github_api_url}/releases/tags/${tag}" | jq -r .url)" | jq -r '.upload_url | sub("{.*"; "")')

    if [[ ! $upload_url == http* ]]; then
        echo "ERROR: Cannot create release on remote github repository."
        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_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_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
}

image_registry_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)" docker.io
        trap "docker logout docker.io" "EXIT"
    fi

    if [ -n "$(readopt --quayio-user)" ] && [ -n "$(readopt --quayio-password)" ]; then
        echo "==== Login to Quay.io"
        docker login -u "$(readopt --quayio-user)" -p "$(readopt --quayio-password)" quay.io
        trap "docker logout quay.io" "EXIT"
    fi
}

create_syndesis_container_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_container_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

push_container_images_to_registry() {
    local registry=$1
    local release_version=$2
    local moving_tag=$3

    echo "==== Pushing to ${registry}"
    for module in $ALL_IMAGE_MODULES; do
        local image="syndesis/syndesis-$module"
        docker tag "$image:$release_version" "$registry/$image:$release_version"
        docker push "$registry/$image:$release_version"

        docker tag "$image:$release_version" "$registry/$image:$moving_tag"
        docker push "$registry/$image:$moving_tag"
    done
}

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

    if [ -n "$(readopt --docker-user)" ] && [ -n "$(readopt --docker-password)" ]; then
      push_container_images_to_registry docker.io "$release_version" "$moving_tag"
    fi

    if [ -n "$(readopt --quayio-user)" ] && [ -n "$(readopt --quayio-password)" ]; then
      push_container_images_to_registry quay.io "$release_version" "$moving_tag"
    fi
}

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|install/operator/build/conf/config.yaml' "chore: release version $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"
            git push -f -u "$remote" "$release_version"
        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 topdir="$1"

    local maven_opts
    maven_opts="--no-transfer-progress --batch-mode -V -e"

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

    local settings_xml
    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

    local gpg_keyname
    gpg_keyname=$(readopt --gpg-keyname)

    local gpg_passphrase
    gpg_passphrase=$(readopt --gpg-passphrase)

    if [ -n "${gpg_keyname}" ] && [ -n "${gpg_passphrase}" ]; then
        maven_opts+=" -Dgpg.keyname=${gpg_keyname} -Dgpg.passphrase=${gpg_passphrase}"
    fi

    if [ "$(hasflag --verbose)" ]; then
        maven_opts="$maven_opts -X"
    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 -E $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
}
