#!/usr/bin/env groovy

@Library(value="magento-saas-pipeline@0.8.14", changelog=false) _
import com.adobe.magento.SecretType

String ARTIFACT_VERSION
String ARTIFACT_RELEASE_URL
String COMPOSER_REPO

pipeline {
    agent {
        label 'dockerbuilder'
    }

    parameters {
        gitParameter(
            name: 'PROMOTED_BUILD',
            defaultValue: '',
            selectedValue: 'NONE',
            type: 'PT_TAG',
            tagFilter: '*alpha*',
            sortMode: 'DESCENDING_SMART',
            description: 'CONDITIONAL. Required for promoting an artifact. Version to promote, e.g. 1.2.3-alpha4')
        string(
            name: 'PROMOTED_SUFFIX',
            defaultValue: '',
            description: 'OPTIONAL. Used when promoting an artifact. Optional custom suffix for the promoted artifact, e.g. rc1')
    }

    options {
        disableConcurrentBuilds()
    }

    environment {
        COMPOSER_URL_INTERNAL           = "connect20-qa01.magedevteam.com"
        COMPOSER_URL_PROD               = "repo.magento.com"
        DOCKER_HUB_REGISTRY_PROXY       = "docker-hub-remote.dr-uw2.adobeitc.com"
        DOCKER_REGISTRY_CREDENTIALS_ID  = "oope-vault-artifactory-cloud-credentials"
        GITHUB_RELEASES_URL             = "https://github.com/magento-commerce/oope-payment-methods/releases/tag/%s"
        GITHUB_CREDENTIALS_ID           = "oope-jenkins-git-com-credentials"
        GITHUB_CORP_CREDENTIALS_ID      = "oope-jenkins-git-corp-credentials"
        JENKINS_BUILD_JOB_INTERNAL      = "Single%20Package"
        JENKINS_BUILD_JOB_PROD          = "Single%20Package-%20PROD"
        JENKINS_PACKAGE_URL             = "magento-packaging-user.ci.corp.adobe.com"
        PACKAGE_NAME                    = "magento/module-out-of-process-payment-methods"
        RELEASE_INFO_BRANCH             = "OOPPM"
        RELEASE_INFO_CONFIG_FILE        = "ops/magento-release-info/0.1.0.yml"
        RELEASE_INFO_DEF_FILE           = "definition.yml"
        RELEASE_INFO_DEF_FILE_SAMPLE    = "ops/magento-release-info/definition.yml"
        RELEASE_INFO_PATH               = "packaging/magento-module-out-of-process-payment-methods"
        SLACK_CHANNEL                   = '#commerce-team-party-parrots-ci'
        VAULT_URL                       = 'https://vault-amer.adobe.net'
        VAULT_CREDENTIALS               = 'oope_vault_jenkins_role_prod'
        VERSION_SETTER_CONFIG_FILE      = "ops/version-setter/config.json"
    }

    stages {
        stage("Prepare magento-release-info") {
            when {
                expression { !params.PROMOTED_BUILD }
            }
            steps {
                script {
                    String version = get_version_from_version_setter()
                    dir('magento-release-info') {
                        create_if_not_exists_magento_release_info_version_yaml(
                            "${version}",
                            "${env.RELEASE_INFO_BRANCH}",
                            "${env.RELEASE_INFO_PATH}",
                            "${env.RELEASE_INFO_CONFIG_FILE}"
                        )
                    }
                }
            }
        }
        stage("Calculate internal version") {
            when {
                expression { !params.PROMOTED_BUILD }
            }
            steps {
                script {
                    ARTIFACT_VERSION = calculate_artifact_internal_version()
                    ARTIFACT_RELEASE_URL = String.format(env.GITHUB_RELEASES_URL, ARTIFACT_VERSION)
                    update_config_file_artifact_version(ARTIFACT_VERSION)
                }
                stash includes: "${env.VERSION_SETTER_CONFIG_FILE}", name: 'config'
            }
        }
        stage("Set module versions") {
            when {
                expression { !params.PROMOTED_BUILD }
                beforeAgent true
            }
            agent {
                docker {
                    image "${env.DOCKER_HUB_REGISTRY_PROXY}/node:18.5"
                    registryUrl "https://${env.DOCKER_HUB_REGISTRY_PROXY}"
                    registryCredentialsId DOCKER_REGISTRY_CREDENTIALS_ID
                    reuseNode true
                }
            }
            steps {
                dir('version-setter') {
                    git(
                        credentialsId: "${env.GITHUB_CORP_CREDENTIALS_ID}",
                        url: 'https://git.corp.adobe.com/magento-payments/version-setter.git',
                        branch: 'main',
                        poll: false,
                    )

                    sh 'npm install typescript'

                    unstash 'config'

                    script {
                        withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
                            sh "git config --global user.email 'party-parrots@adobe.com'"
                            sh "git config --global user.name 'Commerce Domain Services'"
                            sh "npm run version:config -- --config-path=\"${env.VERSION_SETTER_CONFIG_FILE}\""
                        }
                    }
                }
            }
        }
        stage("Build and publish internal") {
            when {
                expression { !params.PROMOTED_BUILD }
            }
            steps {
                script {
                    COMPOSER_REPO = env.COMPOSER_URL_INTERNAL
                    run_remote_jenkins_build_job_and_wait_to_finish(
                        "${env.JENKINS_BUILD_JOB_INTERNAL}",
                        "${ARTIFACT_VERSION}",
                        "${COMPOSER_REPO}"
                    )
                    create_release_tag("${ARTIFACT_VERSION}")
                    delete_remote_branch("${ARTIFACT_VERSION}")
                }
            }
            post {
                success {
                    slackSend(
                        channel: env.SLACK_CHANNEL,
                        color: 'good',
                        message: ":package: Built and pushed *oope-payment-methods <${ARTIFACT_RELEASE_URL}|${ARTIFACT_VERSION}>* to `${COMPOSER_REPO}`  :jenkins_logo: <${env.BUILD_URL}|Open job>",
                    )
                }
                failure {
                    slackSend(
                        channel: env.SLACK_CHANNEL,
                        color: 'danger',
                        message: ":error1: @here Failed to build or push *oope-payment-methods `${ARTIFACT_VERSION ?: '?'}`* to `${COMPOSER_REPO}`  :jenkins_logo: <${env.BUILD_URL}|Open job>",
                    )
                }
            }
        }
        stage("Promotion") {
            when {
                expression { params.PROMOTED_BUILD }
            }
            steps {
                script {
                    ARTIFACT_VERSION = calculate_artifact_promoted_version()
                    ARTIFACT_RELEASE_URL = String.format(env.GITHUB_RELEASES_URL, ARTIFACT_VERSION)

                    String commit = get_commit_from_tag("${params.PROMOTED_BUILD}")
                    if (commit) {
                        create_release_branch("${ARTIFACT_VERSION}", "${commit}")
                        create_release_tag("${ARTIFACT_VERSION}", "${commit}")
                    }

                    echo("[INFO] Promoting version ${ARTIFACT_VERSION} to QA")
                    COMPOSER_REPO = env.COMPOSER_URL_INTERNAL
                    run_remote_jenkins_build_job_and_wait_to_finish("${env.JENKINS_BUILD_JOB_INTERNAL}", "${ARTIFACT_VERSION}", "${COMPOSER_REPO}")

                    echo("[INFO] Promoting version ${ARTIFACT_VERSION} to Prod")
                    COMPOSER_REPO = env.COMPOSER_URL_PROD
                    run_remote_jenkins_build_job_and_wait_to_finish("${env.JENKINS_BUILD_JOB_PROD}", "${ARTIFACT_VERSION}", "${COMPOSER_REPO}")

                    delete_remote_branch("${ARTIFACT_VERSION}")
                }
            }
            post {
                always {
                    script {
                        env.RELEASE_CANDIDATE_URL = String.format(env.GITHUB_RELEASES_URL, params.PROMOTED_BUILD)
                        env.PROMOTED_ARTIFACT_URL = String.format(env.GITHUB_RELEASES_URL, ARTIFACT_VERSION)
                    }
                }
                success {
                    slackSend(
                        channel: env.SLACK_CHANNEL,
                        color: 'good',
                        message: ":medal: Promoted and released *oope-payment-methods <${env.RELEASE_CANDIDATE_URL}|${params.PROMOTED_BUILD}>* " +
                            "as <${ARTIFACT_RELEASE_URL}|${ARTIFACT_VERSION}> to `${COMPOSER_REPO}`  :jenkins_logo: <${env.BUILD_URL}|Open job>",
                    )
                }
                failure {
                    slackSend(
                        channel: env.SLACK_CHANNEL,
                        color: 'danger',
                        message: ":error1: @here Failed to promote *oope-payment-methods <${env.RELEASE_CANDIDATE_URL}|${params.PROMOTED_BUILD}>* to " +
                            "`${COMPOSER_REPO}`  :jenkins_logo: <${env.BUILD_URL}|Open job>",
                    )
                }
            }
        }
    }

    post {
        failure {
            slackSend(
                channel: env.SLACK_CHANNEL,
                color: 'warning',
                message: ":error1: @here *oope-payment-methods* pipeline failed after ${currentBuild.durationString.replace(' and counting', '')}.  " +
                        " :jenkins_logo: <${env.BUILD_URL}|Open job>",
            )
        }
        always {
            cleanWs()
        }
    }
}

def get_version_from_version_setter() {
    String build_version = sh(
        script: "cat ${env.VERSION_SETTER_CONFIG_FILE} | jq -r '.buildVersion'",
        returnStdout: true
    ).trim()

    return build_version
}

def calculate_artifact_internal_version() {
    String build_version = get_version_from_version_setter()
    String artifact_version = build_version + "-alpha${BUILD_NUMBER}"
    buildName artifact_version

    return artifact_version
}

def calculate_artifact_promoted_version() {
    String build = "${params.PROMOTED_BUILD}"
    String artifact_version = build.split("-")[0]

    if ( "${params.PROMOTED_SUFFIX}" != '') {
        String suffix = "${params.PROMOTED_SUFFIX}"
        artifact_version += '-' + suffix.replaceAll("[^a-zA-Z0-9]+","")
    }

    currentBuild.displayName = artifact_version
    return artifact_version
}

def update_config_file_artifact_version(version) {
    sh """
        jq '.buildVersion="${version}"' ${env.VERSION_SETTER_CONFIG_FILE} > ${env.VERSION_SETTER_CONFIG_FILE}.tmp
        jq '.buildBranch="${version}"' ${env.VERSION_SETTER_CONFIG_FILE}.tmp > ${env.VERSION_SETTER_CONFIG_FILE}
        rm ${env.VERSION_SETTER_CONFIG_FILE}.tmp
        cat ${env.VERSION_SETTER_CONFIG_FILE}
    """
}

def create_if_not_exists_magento_release_info_version_yaml(version, branch, path, sample) {
    withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
        sh """
            git clone https://github.com/magento-commerce/magento-release-info.git .
            git checkout ${branch} 2>/dev/null || git checkout -b ${branch}
            mkdir -p "${path}"
            if [ ! -f "${path}/${env.RELEASE_INFO_DEF_FILE}" ]; then
                cp "${env.WORKSPACE}/${env.RELEASE_INFO_DEF_FILE_SAMPLE}" ${path}/${env.RELEASE_INFO_DEF_FILE}
                git add ${path}/${env.RELEASE_INFO_DEF_FILE}
                git commit -m "create initial ${env.RELEASE_INFO_DEF_FILE} file"
                git push origin ${branch}
            fi
            if [ ! -f "${path}/${version}.yml" ]; then
                cp "${env.WORKSPACE}/${sample}" ${path}/${version}.yml
                git add ${path}/${version}.yml
                git commit -m "bump version ${version}"
                git push origin ${branch}
            fi
        """
    }
}

def run_remote_jenkins_build_job_and_wait_to_finish(job, version, repo) {
    withVaultSecrets(
        env.VAULT_URL,
        env.VAULT_CREDENTIALS,
        [
            'dx_commerce_order/generic_account/jenkins/magento-packaging-user': [
                'JENKINS_USER': 'username',
                'JENKINS_TOKEN': 'token'
            ]
        ]) {
        String next_build = get_jenkins_next_build(job)
        start_jenkins_build_job(job, version, repo)
        wait_for_jenkins_build_execution(job, next_build)
    }
}

def start_jenkins_build_job(job, version, repo) {
    sh """
        CRUMB_NAME=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumbRequestField'`
        CRUMB_VALUE=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumb'`

        curl -s -X POST https://${env.JENKINS_PACKAGE_URL}/job/${job}/build \
            --user ${JENKINS_USER}:${JENKINS_TOKEN} \
            -H "\$CRUMB_NAME:\$CRUMB_VALUE" \
            --data-urlencode json='{"parameter": [{"name": "packageName", "value":"${env.PACKAGE_NAME}"},{"name":"versionWithSuffix", "value":"${version}"}, {"name":"releaseInfoBranch", "value":"${env.RELEASE_INFO_BRANCH}"}, {"name":"composerRepo", "value":"${repo}"}]}'
    """
}

def get_jenkins_next_build(job) {
    String next_build = sh(
        script: """
                CRUMB_NAME=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumbRequestField'`
                CRUMB_VALUE=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumb'`

                curl -s -X GET https://${env.JENKINS_PACKAGE_URL}/job/${job}/api/json \
                    --user ${JENKINS_USER}:${JENKINS_TOKEN} \
                    -H "\$CRUMB_NAME:\$CRUMB_VALUE" | jq '.nextBuildNumber'
                """,
        returnStdout: true
    ).trim()

    return next_build
}

def get_prev_artifact_version() {
    return sh( script: "git describe --match '[0-9]*.[0-9]*.[0-9]' --abbrev=0", returnStdout: true ).trim()
}

def wait_for_jenkins_build_execution(job, build) {
    String result = "null"
    int timeout = 0

    while(result == "null" && timeout < 60) {
        sleep(10)

        result = sh(
            script: """
                    CRUMB_NAME=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumbRequestField'`
                    CRUMB_VALUE=`curl -s --user ${JENKINS_USER}:${JENKINS_TOKEN} "https://${env.JENKINS_PACKAGE_URL}/crumbIssuer/api/json" | jq -r '.crumb'`

                    curl -s -X GET https://${env.JENKINS_PACKAGE_URL}/job/${job}/${build}/api/json \
                        --user ${JENKINS_USER}:${JENKINS_TOKEN} \
                        -H "\$CRUMB_NAME:\$CRUMB_VALUE" | jq -r '.result'
                    """,
            returnStdout: true
        ).trim()

        timeout++
    }

    if(timeout == 60) {
        error("Timeout while waiting for https://${env.JENKINS_PACKAGE_URL}/job/${job}/${build}/ to finish")
    } else {
        if(result != "SUCCESS") {
            error("Build https://${env.JENKINS_PACKAGE_URL}/job/${job}/${build}/ finished in status ${result}")
        }
    }
}

def create_and_merge_pr(repo, version) {
    // We need two authentication methods for git and GitHub API
    //      https://github.com/github/hub/issues/1644

    withVaultSecrets(
        env.VAULT_URL,
        env.VAULT_CREDENTIALS,
        [
            'dx_commerce_order/generic_account/github-com': [
                'GH_TOKEN':'token'
            ]
        ]) {
        withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
            dir("tmp-merge") {
                Integer exit_code = sh(
                    script:"""
                        echo "[INFO] Creating and merging PR"
                        git clone $repo -b $version .
                        gh pr create -B main -t "CI/CD - $version composer update" -f
                        gh pr view $version
                        if [ \$(gh pr diff $version | wc -l) == 0 ]; then
                            echo "[INFO] The PR $version has no changes"
                            exit 1
                        else
                            gh pr merge $version --squash --delete-branch --auto --body "CI/CD auto-merge"
                        fi
                    """,
                    returnStatus: true
                )
                return exit_code
            }
        }
    }
}

def create_release_tag(version, commit="") {
    withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
        sh """
            echo "[INFO] Creating and pushing tag"
            git config user.email 'party-parrots@adobe.com'
            git config user.name 'Commerce Domain Services'
            if [ "$commit" == "" ]; then
                git tag -a $version \$(git log --format="%H" -n 1) -m"$version"
            else
                git tag -a $version $commit -m"$version"
            fi
            git push origin --tags
        """
    }
}

def create_release_branch(version, commit) {
    withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
        sh """
            echo "[INFO] Creating a release branch for $version"
            git checkout -b $version $commit
            git push origin $version
        """
    }
}

def delete_remote_branch(version) {
    withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
        sh """
            echo "[INFO] Deleting remote branch $version"
            git push --delete origin refs/heads/$version
        """
    }
}

def get_commit_from_tag(version) {
    withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CREDENTIALS_ID}", gitToolName: 'git')]) {
        String commit = sh(
            script: """
                git rev-list -n 1 tags/$version
            """,
            returnStdout: true
        ).trim()
        return commit
    }
}
