/*
Config mandatory properties:
path

Optional properties:
enableDestroy=true
tfvarParams - list of maps with parameters
slackChannel
secrets - map of env vars with secret paths
secretsProfile - set profile used to fetch secrets (gdb-infra-dev by default)
autoApplyByDefault
cronSchedule
*/
def call(body) {
    def config = [:]
    body.resolveStrategy = Closure.DELEGATE_FIRST
    body.delegate = config
    body()

    if (!config.containsKey('path')) {
        error 'path is mandatory'
    }
    // Defaults for unset parameters
    if (!config.containsKey('enableDestroy')) { config.enableDestroy = false }
    if (!config.containsKey('autoApplyByDefault')) { config.autoApplyByDefault = false }
    if (!config.containsKey('secrets')) { config.secrets = [:] }
    if (!config.containsKey('secretsProfile')) { config.secretsProfile = "gdb-infra-dev" }
    if (!config.containsKey('tfvarConst')) { config.tfvarConst = [] }
    if (!config.containsKey('tfvarParams')) { config.tfvarParams = [:] }
    if (!config.containsKey('slackChannel')) { config.slackChannel = 'terraform-cicd-alerts' }

    optionalParams = []
    if (config.enableDestroy) {
        optionalParams << booleanParam(name: 'TERRAFORM_DESTROY',
                                    defaultValue: false,
                                    description: 'Destroy all resources in the state')
    }
    if (config.containsKey('tfvarParams')) {
        optionalParams << separator(name: 'terraform-custom-variables',
                sectionHeader: 'Custom terraform variables')
        tfParams = utility.paramsFromListOfMaps(config.tfvarParams)
        optionalParams = ( optionalParams << tfParams).flatten()
    }

    mandatoryParams = [
        choice(
            name: 'TERRAFORM_APPLY',
            choices: [ 'default', 'no', 'yes' ],
            description: 'Whether to run prompt for apply and than apply itself. By default only enabled for master branch'
        ),
        booleanParam(
            name: 'SKIP_APPROVAL',
            defaultValue: false,
            description: 'Skip approval step and do the changes right away. SUPER GIGA DANGEROUS'
        )
    ]
    if (config.containsKey('cronSchedule')) {
        properties([
            parameters(( mandatoryParams << optionalParams).flatten() ),
            pipelineTriggers([cron(config.cronSchedule)])
        ])
    } else {
        properties([parameters(( mandatoryParams << optionalParams).flatten() )])
    }

    pipeline {
        agent {
            label 'terraform-agent'
        }

        options {
            disableConcurrentBuilds()
            ansiColor('xterm')
            lock("${config.path}")
        }

        environment {
            GITHUB_MAX_COMMENT_LENGTH = 65530
            TF_IN_AUTOMATION = 1
            STATE_PATH = "${WORKSPACE}/${config.path}"
            DESTROY_MODIFIER = "${params.TERRAFORM_DESTROY ? '-destroy' : ' '}"
        }

        stages {
            stage('Set dynamic env variables') {
                steps { script { utility.loadEnvVarsFromSecrets(config.secrets, config.secretsProfile) } }
            }
            stage('Print params') {
                steps { script { println(params) } }
            }
            stage('Github notify') {
                steps { githubNotify description: 'Build has started',  status: 'PENDING', context: config.path }
            }
            stage('Generate custom tfvars') {
                steps {
                    script {
                        if ( config.tfvarParams ) {
                            terraform.generateTfvarsAuto(config.tfvarParams, params, env.STATE_PATH)
                        }
                    }
                }
            }
            stage('Init') {
                steps {
                    sh "cd ${env.STATE_PATH} && terraform -chdir=remote get -update"
                    sh "cd ${env.STATE_PATH} && terraform -chdir=remote init -input=false -backend-config=../backend.tfvars"
                }
            }
            stage('Validate') {
                steps {
                    sh "cd ${env.STATE_PATH} && terraform -chdir=remote validate -no-color"
                }
            }
            stage('Plan') {
                options {
                    timeout(time: 20, unit: 'MINUTES')
                }
                steps {
                    script {
                        def tf_plan = sh(
                            script: """
                                set -euxo pipefail;
                                cd ${env.STATE_PATH} && terraform -chdir=remote plan ${env.DESTROY_MODIFIER} --out=plan.tfplan -no-color -input=false -var-file=../terraform.tfvars >/dev/null && \
                                terraform -chdir=remote show -no-color plan.tfplan
                                """,
                            returnStdout: true
                            ).trim()
                        def tf_plan_only_actions = terraform.removeChangesDriftFromPlan(tf_plan)
                        println(tf_plan_only_actions)
                        changes = terraform.checkChangesInPlan(tf_plan)
                        //skip_approval = params.SKIP_APPROVAL || terraform.autoApplyMergedPR()
                        skip_approval = params.SKIP_APPROVAL
                        if (changes.changesInPlan && terraform.checkBranchConditions(params.TERRAFORM_APPLY) && !skip_approval) {
                            githubNotify description: "Waiting for approval: ${changes.message}",  status: 'PENDING', context: config.path
                            utility.slackNotify(currentBuild.currentResult, config.slackChannel, "Waiting for approval: ${changes.message}")
                            input "${changes.message}\nConfirmation for applying the plan"
                        }
                        // Put plan in the PR comment if there is CHANGE_TARGET
                        else if (env.CHANGE_TARGET && changes.changesInPlan) {
                            if (tf_plan_only_actions.length() < env.GITHUB_MAX_COMMENT_LENGTH.toInteger()) {
                                pullRequest.comment("<details><summary>Terraform plan</summary>\n\n```${tf_plan_only_actions}\n</details>")
                            }
                            else {
                                pullRequest.comment("The plan is too big to post, visit [build page](${env.BUILD_URL}) to check it out.")
                            }
                        }
                    }
                }
            }
            stage('Apply') {
                when {
                    expression { changes.changesInPlan && terraform.checkBranchConditions(params.TERRAFORM_APPLY) }
                }
                options {
                    timeout(time: 1, unit: 'HOURS')
                }
                steps {
                    script {
                        sh(
                          script: """
                              set -euxo pipefail;
                              cd ${env.STATE_PATH} && terraform -chdir=remote apply ${env.DESTROY_MODIFIER} -auto-approve -no-color -input=false plan.tfplan
                            """,
                          returnStdout: true
                        ).trim()
                        changes.message += ' Changes has been applied!'
                    }
                }
            }
        }
        post {
            always {
                sh "sudo chown -R ec2-user:ec2-user ${env.WORKSPACE}"
            }
            success {
                script {
                    utility.slackNotify(currentBuild.currentResult, config.slackChannel, changes.message)
                }
                githubNotify description: "${changes.message}",  status: 'SUCCESS', context: config.path
            }
            failure {
                script {
                    utility.slackNotify(currentBuild.currentResult, config.slackChannel)
                }
                githubNotify description: 'Plan failed',  status: 'FAILURE', context: config.path
            }
            cleanup {
                deleteDir()
            }
        }
    }
}
