List<String> AWS_REGIONS = ['us-east-1']

String SHARED_ACCOUNT_ID = '086679231553'
String PROD_ACCOUNT_ID = '009424872958'
String QA_ACCOUNT_ID = '814622134907'

String PROD_DEPLOY_ROLE = 'prod-jenkins-pipeline-deploy-role'
String QA_DEPLOY_ROLE = 'qa-jenkins-pipeline-deploy-role'
String BUILD_ROLE = 'qa-business-solutions-automations-integration-test-role'

PROJECTS = [
    'lambda-business-solutions-automations-publishing-client-report': [
        additionalContexts: [
            common: '../../common'
        ],
        dir: 'lambdas/publishing_client_report',
        serviceType: 'LAMBDA',
        deployToQA: true,
        qaFunctionName: 'qa-lambda-business-solutions-automations-pub_clt_rpt',
        deployToProd: true,
        prodFunctionName: 'prod-lambda-business-solutions-automations-pub_clt_rpt'
    ],
    'lambda-business-solutions-automations-snowflake-query': [
        additionalContexts: [
            common: '../../common'
        ],
        dir: 'lambdas/snowflake_query',
        serviceType: 'LAMBDA',
        deployToQA: true,
        qaFunctionName: 'qa-lambda-business-solutions-automations-sf_query',
        deployToProd: true,
        prodFunctionName: 'prod-lambda-business-solutions-automations-sf_query'
    ],
        'lambda-business-solutions-automations-send-email': [
        additionalContexts: [
            common: '../../common'
        ],
        dir: 'lambdas/send_email',
        serviceType: 'LAMBDA',
        deployToQA: true,
        qaFunctionName: 'qa-lambda-business-solutions-automations-send_email',
        deployToProd: true,
        prodFunctionName: 'prod-lambda-business-solutions-automations-send_email',
    ],
]

@groovy.transform.Field
Map projectsToBuild = null

pipeline {
    agent {
        label 'aws'
    }

    options {
        timestamps()
        disableConcurrentBuilds()
    }

    parameters {
        choice(name: 'PROJECT_NAME', choices: [''] + PROJECTS.keySet(), description: 'The project to build. If not specified, all modified projects will be built.')
        booleanParam(name: 'DEPLOY_TO_PROD', defaultValue: true, description: 'Whether or not to deploy to prod.')
        string(name: 'SHARED_LIBRARIES_VERSION', defaultValue: 'master', description: 'The version of the Jenkins shared libraries to use. Can be a branch, tag or Git revision.')
    }

    triggers {
        issueCommentTrigger('.*retest this please.*')
    }

    stages {
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }
        stage('Compliance Checks') {
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, config ->
                        [
                            (project): {
                                dir(config['dir']) {
                                    complianceChecks()
                                }
                            }
                        ]
                    })
                }
            }
        }
        stage('Validate Software Catalog Definition') {
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, config ->
                        [
                            (project): {
                                datadogSoftwareCatalogValidate(servicePath: config['dir'])
                            }
                        ]
                    })
                }
            }
        }
        stage('Unit Tests and Style Checks - Common Module') {
            when {
                expression { getModifiedPaths(paths: ['common']) }
            }
            steps {
                dir('common') {
                withEcr {
                    sh 'make build_test'
                    sh 'make lint_and_test'
                   }
                }
            }
        }
        stage('Unit Tests and Style Checks') {
            steps {
                script {
                    withSecrets(awsAccountId: '814622134907', awsRole: BUILD_ROLE, secrets: [
                        [id: 'qa/business-solutions-automations/SNOWFLAKE_QUERY_SNOWFLAKE_PRIVATE_KEY', environmentVariable: 'SNOWFLAKE_PRIVATE_KEY_CONTENT'],
                    ]) {
                        parallel(getProjectsToBuild().collectEntries { project, config ->
                            return [
                                (project): {
                                    dir(config['dir']) {
                                        withEcr {
                                            sh 'make build_test'
                                            sh 'make lint_and_test'
                                        }
                                    }
                                }
                            ]
                        })
                    }
                }
            }
        }
        stage('Create a Release') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                }
            }
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, config ->
                        [
                            (project): {
                                dockerToEcr awsRegions: AWS_REGIONS,
                                    ecrAccountId: SHARED_ACCOUNT_ID,
                                    imageName: project,
                                    imageTag: env.GIT_COMMIT,
                                    dockerBuildContext: config['dir'],
                                    dockerBuildFile: "${config['dir']}/Dockerfile",
                                    dockerBuildTarget: config['dockerBuildTarget'],
                                    additionalContexts: config['additionalContexts']
                            }
                        ]
                    })
                }
            }
        }
        stage('Deploy QA') {
            when {
                branch 'master'
            }
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, projectConfig ->
                        def deployToQA = projectConfig.get('deployToQA', true)
                        if (deployToQA) {
                            [
                                "${project}-qa": {
                                    if (projectConfig['serviceType'] == 'LAMBDA') {
                                        lambdaDeploy environment: 'qa',
                                            functionName: projectConfig['qaFunctionName'],
                                            awsRegions: AWS_REGIONS,
                                            imageTag: env.GIT_COMMIT,
                                            imageName: project,
                                            ecrRegistryAccountId: SHARED_ACCOUNT_ID,
                                            awsDeploymentTargetAccountId: QA_ACCOUNT_ID,
                                            awsDeploymentRoleName:QA_DEPLOY_ROLE
                                    }
                                    else {
                                        fargateDeploy environment:'qa',
                                            awsRegions: AWS_REGIONS,
                                            gitCommit: env.GIT_COMMIT,
                                            serviceName: (projectConfig['qaServiceName'] ?: project),
                                            ecrRegistryAccountId: SHARED_ACCOUNT_ID,
                                            awsDeploymentTargetAccountId: QA_ACCOUNT_ID,
                                            awsDeploymentRoleName: QA_DEPLOY_ROLE,
                                            deployType: projectConfig['deployType'],
                                            imageNameOverride: project
                                    }
                                }
                            ]
                        }
                        else {
                            echo "Skipping deployment to QA for ${project} as deployToQA is set to false."
                        }
                    })
                }
            }
        }
        stage('Integration Tests') {
            when {
                branch 'master'
            }
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, config ->
                        [
                            (project): {
                                dir(config['dir']) {
                                    withEcr {
                                        sh 'make integration_test'
                                    }
                                }
                            }
                        ]
                    })
                }
            }
        }
        stage('Deploy Production') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                script {
                    parallel(getProjectsToBuild().collectEntries { project, projectConfig ->
                        def deployToProd = projectConfig.get('deployToProd', true)
                        if (deployToProd) {
                             [
                                  "${project}-prod": {
                                      if (projectConfig['serviceType'] == 'LAMBDA') {
                                          lambdaDeploy environment: 'prod',
                                              functionName: projectConfig['prodFunctionName'],
                                              awsRegions: AWS_REGIONS,
                                              imageTag: env.GIT_COMMIT,
                                              imageName: project,
                                              ecrRegistryAccountId: SHARED_ACCOUNT_ID,
                                              awsDeploymentTargetAccountId: PROD_ACCOUNT_ID,
                                              awsDeploymentRoleName: PROD_DEPLOY_ROLE
                                      }
                                      else {
                                          fargateDeploy environment: 'prod',
                                              awsRegions: AWS_REGIONS,
                                              gitCommit: env.GIT_COMMIT,
                                              serviceName: (projectConfig['prodServiceName'] ?: project),
                                              ecrRegistryAccountId: SHARED_ACCOUNT_ID,
                                              awsDeploymentTargetAccountId: PROD_ACCOUNT_ID,
                                              awsDeploymentRoleName: PROD_DEPLOY_ROLE,
                                              deployType: projectConfig['deployType'],
                                              imageNameOverride: project
                                      }
                                      datadogSoftwareCatalogPublish(servicePath: projectConfig['dir'])
                                  }
                             ]
                        }
                        else {
                            echo "Skipping deployment to Production for ${project} as deployToProd is set to false."
                        }
                    })
                }
            }
        }
    }
}

def isCommonModuleChanged() {
    return getModifiedPaths(paths: ['common']) as boolean
}

def getProjectsToBuild() {
    if (this.@projectsToBuild != null) {
        return this.@projectsToBuild
    }

    this.@projectsToBuild = [:]

    if (params.PROJECT_NAME) {
        this.@projectsToBuild << PROJECTS.find{ it.key == params.PROJECT_NAME }
    }
    else {
        if (isCommonModuleChanged()) {
            // Build all projects that depend on common module
            this.@projectsToBuild << PROJECTS.findAll{it.value.additionalContexts?.common}
        }
        def projectPaths = PROJECTS.collect{ it.value['dir'] }
        def modifiedPaths = getModifiedPaths(paths: projectPaths)
        this.@projectsToBuild << PROJECTS.findAll{modifiedPaths.contains(it.value['dir']) }
    }
    currentBuild.description = this.@projectsToBuild.keySet().join('<br>')

    return this.@projectsToBuild
}
