import groovy.transform.Field

@Field def GITHUB_REPOSITORY = 'dbt-fansifter-shopify'
@Field def ECR_ACCOUNT_ID = '086679231553'
List<String> AWS_REGIONS = ['us-east-1']
String QA_ACCOUNT_ID = '437795906767'
String QA_DEPLOYMENT_ROLE = 'qa-dbt-audience-deploy-role'
String QA_SESSION_NAME = "qa-dbt-fansifter-shopify-session"
String PROD_ACCOUNT_ID = '437795906767'
String PROD_DEPLOYMENT_ROLE = 'prod-dbt-audience-deploy-role'
String PROD_SESSION_NAME = "prod-dbt-fansifter-shopify-session"
String SLACK_NOTIFICATIONS_CHANNEL = '#fansifter-alerts'
@Field def SNOWFLAKE_ACCOUNT = 'delphi.us-east-1'
@Field def SNOWFLAKE_DATABASE = 'FANSIFTER_SHOPIFY'
String QA_SNOWFLAKE_USER = 'QA_FANSIFTER_DBT_SCHEDULER'
String QA_SNOWFLAKE_ROLE = 'QA_FANSIFTER_DBT'
String QA_SNOWFLAKE_WAREHOUSE = 'QA_DBT_WH'
String QA_SNOWFLAKE_PRIVATE_KEY_SECRET_ID = 'qa/dbt-audience/DBT_SCHEDULER_SNOWFLAKE_PRIVATE_KEY'
String QA_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID = 'qa/dbt-audience/DBT_SCHEDULER_SNOWFLAKE_KEY_PASSPHRASE'
String PROD_SNOWFLAKE_USER = 'PROD_FANSIFTER_DBT_SCHEDULER'
String PROD_SNOWFLAKE_ROLE = 'PROD_FANSIFTER_DBT'
String PROD_SNOWFLAKE_SCHEMA = 'PROD'
String PROD_SNOWFLAKE_WAREHOUSE = 'PROD_DBT_WH'
String PROD_SNOWFLAKE_PRIVATE_KEY_SECRET_ID = 'prod/dbt-audience/DBT_SCHEDULER_SNOWFLAKE_PRIVATE_KEY'
String PROD_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID = 'prod/dbt-audience/DBT_SCHEDULER_SNOWFLAKE_KEY_PASSPHRASE'

def getSecretValue(String deploymentRole, String accountId, String sessionName, String region, String secretId) {
    withAWS(role: deploymentRole, roleAccount: accountId, roleSessionName: sessionName, region: region, useNode: true) {
        return sh(
            script: "aws secretsmanager get-secret-value --secret-id ${secretId} --query SecretString --output text",
            returnStdout: true
        ).trim()
    }
}

def runLintAndTest(Map args = [:]) {
    def deploymentRole = args.deploymentRole
    def accountId = args.accountId
    def sessionName = args.sessionName
    def region = args.region
    def snowflakePrivateKeySecretId = args.snowflakePrivateKeySecretId
    def snowflakeKeyPassphraseSecretId = args.snowflakeKeyPassphraseSecretId

    def snowflakePrivateKey = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakePrivateKeySecretId
    )
    def snowflakeKeyPassphrase = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakeKeyPassphraseSecretId
    )
    def snowflakeUser = args.snowflakeUser
    def snowflakeRole = args.snowflakeRole
    def snowflakeWarehouse = args.snowflakeWarehouse
    def snowflakeSchema = args.snowflakeSchema
    def dbtFullRefresh = args.dbtFullRefresh ?: true
    def dbtSelectModels = args.dbtSelectModels ?: ''

    def envVars = [
        "SNOWFLAKE_ACCOUNT=${SNOWFLAKE_ACCOUNT}",
        "SNOWFLAKE_DATABASE=${SNOWFLAKE_DATABASE}",
        "SNOWFLAKE_USER=${snowflakeUser}",
        "SNOWFLAKE_ROLE=${snowflakeRole}",
        "SNOWFLAKE_WAREHOUSE=${snowflakeWarehouse}",
        "SNOWFLAKE_SCHEMA=${snowflakeSchema}",
        "SNOWFLAKE_PRIVATE_KEY=${snowflakePrivateKey}",
        "SNOWFLAKE_PRIVATE_KEY_PASSPHRASE=${snowflakeKeyPassphrase}",
    ]

    if (dbtFullRefresh) {
        envVars.add("DBT_FULL_REFRESH=false")
    }

    if (dbtSelectModels) {
        envVars.add("DBT_SELECT_MODELS=${dbtSelectModels}")
    }

    withEnv(envVars) {
        sh "make ci_lint_and_test"
    }
}

def isScheduler = env.JOB_NAME.contains("scheduler")
def cronSchedule = isScheduler ? 'TZ=PST\n10 * * * *' : ''

def runDbt(Map args = [:]) {
    def deploymentRole = args.deploymentRole
    def accountId = args.accountId
    def sessionName = args.sessionName
    def region = args.region
    def snowflakePrivateKeySecretId = args.snowflakePrivateKeySecretId
    def snowflakeKeyPassphraseSecretId = args.snowflakeKeyPassphraseSecretId

    def snowflakePrivateKey = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakePrivateKeySecretId
    )
    def snowflakeKeyPassphrase = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakeKeyPassphraseSecretId
    )
    def deployTag = env.JOB_NAME.contains("scheduler") ? 'latest' : env.GIT_COMMIT
    def deployImage = "${ECR_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/${GITHUB_REPOSITORY}:${deployTag}"
    def dbtTarget = args.dbtTarget
    def dbtSelectModels = args.dbtSelectModels ?: ''
    def dbtFullRefresh = args.dbtFullRefresh ?: false
    def snowflakeUser = args.snowflakeUser
    def snowflakeRole = args.snowflakeRole
    def snowflakeWarehouse = args.snowflakeWarehouse
    def snowflakeSchema = args.snowflakeSchema

    def envVars = [
        "DOCKER_CONFIG=${env.WORKSPACE}/.docker",
        "SNOWFLAKE_ACCOUNT=${SNOWFLAKE_ACCOUNT}",
        "SNOWFLAKE_DATABASE=${SNOWFLAKE_DATABASE}",
        "SNOWFLAKE_USER=${snowflakeUser}",
        "SNOWFLAKE_ROLE=${snowflakeRole}",
        "SNOWFLAKE_WAREHOUSE=${snowflakeWarehouse}",
        "SNOWFLAKE_SCHEMA=${snowflakeSchema}",
        "SNOWFLAKE_PRIVATE_KEY=${snowflakePrivateKey}",
        "SNOWFLAKE_PRIVATE_KEY_PASSPHRASE=${snowflakeKeyPassphrase}",
        "DBT_TARGET=${dbtTarget}",
    ]

    if (dbtFullRefresh) {
        envVars.add("DBT_FULL_REFRESH=false")
    }

    if (dbtSelectModels) {
        envVars.add("DBT_SELECT_MODELS=${dbtSelectModels}")
    }

    // Override DOCKER_CONFIG to directory local to workspace to ensure authentication is isolated from other jobs
    withEnv(envVars) {
        echo 'Logging in to ECR registry'
        def ecrRegion = deployImage.split(/\./)[3]
        def ecrRegistry = deployImage.split(/\//)[0]
        sh "aws ecr get-login-password --region ${ecrRegion} | docker login --username AWS --password-stdin ${ecrRegistry}"

        echo 'Pulling image'
        sh "docker pull ${deployImage}"
        sh "docker run -e SNOWFLAKE_ACCOUNT -e SNOWFLAKE_DATABASE -e SNOWFLAKE_USER -e SNOWFLAKE_ROLE -e SNOWFLAKE_WAREHOUSE -e SNOWFLAKE_SCHEMA -e SNOWFLAKE_PRIVATE_KEY -e SNOWFLAKE_PRIVATE_KEY_PASSPHRASE -e DBT_TARGET -e DBT_FULL_REFRESH -e DBT_BUILD -e DBT_SELECT_MODELS ${deployImage}"
    }
}

def runTest(Map args = [:]) {
    def deploymentRole = args.deploymentRole
    def accountId = args.accountId
    def sessionName = args.sessionName
    def region = args.region
    def snowflakePrivateKeySecretId = args.snowflakePrivateKeySecretId
    def snowflakeKeyPassphraseSecretId = args.snowflakeKeyPassphraseSecretId

    def snowflakePrivateKey = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakePrivateKeySecretId
    )
    def snowflakeKeyPassphrase = getSecretValue(
        deploymentRole = deploymentRole,
        accountId = accountId,
        sessionName = sessionName,
        region = region,
        secretId = snowflakeKeyPassphraseSecretId
    )
    def snowflakeUser = args.snowflakeUser
    def snowflakeRole = args.snowflakeRole
    def snowflakeWarehouse = args.snowflakeWarehouse
    def snowflakeSchema = args.snowflakeSchema
    def dbtTarget = args.dbtTarget

    def envVars = [
        "SNOWFLAKE_ACCOUNT=${SNOWFLAKE_ACCOUNT}",
        "SNOWFLAKE_DATABASE=${SNOWFLAKE_DATABASE}",
        "SNOWFLAKE_USER=${snowflakeUser}",
        "SNOWFLAKE_ROLE=${snowflakeRole}",
        "SNOWFLAKE_WAREHOUSE=${snowflakeWarehouse}",
        "SNOWFLAKE_SCHEMA=${snowflakeSchema}",
        "SNOWFLAKE_PRIVATE_KEY=${snowflakePrivateKey}",
        "SNOWFLAKE_PRIVATE_KEY_PASSPHRASE=${snowflakeKeyPassphrase}",
        "DBT_TARGET=${dbtTarget}",
    ]

    withEnv(envVars) {
        sh "make dbt_test"
    }
}

pipeline {
    agent {
        label 'aws'
    }

    options {
        ansiColor('xterm')
        disableConcurrentBuilds()
        timestamps()
        skipDefaultCheckout(true)
    }

    parameters {
        booleanParam(name: 'RUN_ON_QA', defaultValue: true, description: 'Whether or not to run on qa.')
        booleanParam(name: 'RUN_ON_PROD', defaultValue: false, description: 'Whether or not to run on prod.')
        booleanParam(name: 'FULL_REFRESH', defaultValue: false, description: 'Whether or not to run a full refresh.')
        string(name: 'SELECT_MODELS', defaultValue: '', description: 'Specify which models should be run (--select).')
        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.')
        booleanParam(name: 'RUN_QA_TESTS', defaultValue: true, description: 'Whether or not to run only tests on qa.')
        booleanParam(name: 'RUN_PROD_TESTS', defaultValue: true, description: 'Whether or not to run only tests on prod.')
    }

    triggers {
        cron(cronSchedule)
    }

    stages {
        stage('Cleanup Workspace') {
            steps {
                cleanWs()
            }
        }
        stage('Checkout Code') {
            steps {
                script {
                    def scmVars = checkout scm
                    env.GIT_COMMIT = scmVars.GIT_COMMIT
                    env.BRANCH_NAME = scmVars.GIT_BRANCH.tokenize("/").last()
                }
            }
        }
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }
        stage('Compliance Checks') {
            when {
                expression { !isScheduler }
            }
            steps {
                complianceChecks()
            }
        }
        stage('Style Checks And Unit Tests') {
            when {
                expression { !isScheduler }
            }
            steps {
                script {
                    def snowflakeSchema = env.BRANCH_NAME == 'master' ? 'CI_TEST' : 'CI_PR'
                    def dbtFullRefresh = params.FULL_REFRESH
                    runLintAndTest(
                        deploymentRole: QA_DEPLOYMENT_ROLE,
                        accountId: QA_ACCOUNT_ID,
                        sessionName: QA_SESSION_NAME,
                        region: AWS_REGIONS[0],
                        snowflakeUser: QA_SNOWFLAKE_USER,
                        snowflakeRole: QA_SNOWFLAKE_ROLE,
                        snowflakeWarehouse: QA_SNOWFLAKE_WAREHOUSE,
                        snowflakeSchema: snowflakeSchema,
                        snowflakePrivateKeySecretId: QA_SNOWFLAKE_PRIVATE_KEY_SECRET_ID,
                        snowflakeKeyPassphraseSecretId: QA_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID,
                        dbtFullRefresh: dbtFullRefresh,
                        dbtSelectModels: params.SELECT_MODELS,
                    )
                }
            }
        }
        stage('Create a Release') {
            when {
                branch 'master'
                expression { !isScheduler }
            }
            steps {
                dockerToEcr awsRegions: AWS_REGIONS, ecrAccountId: ECR_ACCOUNT_ID, imageName: GITHUB_REPOSITORY, imageTag: env.GIT_COMMIT,
                    dockerBuildTarget: 'deploy'
            }
        }
        stage('Run on QA') {
            when {
                allOf {
                    branch 'master'
                    expression { return params.RUN_ON_QA || isScheduler }
                }
            }
            steps {
                runDbt(
                    deploymentRole: QA_DEPLOYMENT_ROLE,
                    accountId: QA_ACCOUNT_ID,
                    sessionName: QA_SESSION_NAME,
                    region: AWS_REGIONS[0],
                    dbtFullRefresh: params.FULL_REFRESH,
                    dbtSelectModels: params.SELECT_MODELS,
                    dbtTarget: 'qa',
                    snowflakeUser: QA_SNOWFLAKE_USER,
                    snowflakeRole: QA_SNOWFLAKE_ROLE,
                    snowflakeWarehouse: QA_SNOWFLAKE_WAREHOUSE,
                    snowflakeSchema: 'QA',
                    snowflakePrivateKeySecretId: QA_SNOWFLAKE_PRIVATE_KEY_SECRET_ID,
                    snowflakeKeyPassphraseSecretId: QA_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID,
                )
            }
        }
        stage('Run QA tests') {
            when {
                allOf {
                    branch 'master'
                    expression { return params.RUN_QA_TESTS || isScheduler }
                }
            }
            steps {
                script {
                    runTest(
                        deploymentRole: QA_DEPLOYMENT_ROLE,
                        accountId: QA_ACCOUNT_ID,
                        sessionName: QA_SESSION_NAME,
                        region: AWS_REGIONS[0],
                        snowflakeUser: QA_SNOWFLAKE_USER,
                        snowflakeRole: QA_SNOWFLAKE_ROLE,
                        snowflakeWarehouse: QA_SNOWFLAKE_WAREHOUSE,
                        snowflakeSchema: 'QA',
                        snowflakePrivateKeySecretId: QA_SNOWFLAKE_PRIVATE_KEY_SECRET_ID,
                        snowflakeKeyPassphraseSecretId: QA_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID,
                        dbtTarget: 'qa',
                    )
                }
            }
        }
        stage('Run on PROD') {
            when {
                allOf {
                    branch 'master'
                    expression { return params.RUN_ON_PROD || isScheduler }
                }
            }
            steps {
                runDbt(
                    deploymentRole: PROD_DEPLOYMENT_ROLE,
                    accountId: PROD_ACCOUNT_ID,
                    sessionName: PROD_SESSION_NAME,
                    region: AWS_REGIONS[0],
                    dbtFullRefresh: params.FULL_REFRESH,
                    dbtSelectModels: params.SELECT_MODELS,
                    dbtTarget: 'prod',
                    snowflakeUser: PROD_SNOWFLAKE_USER,
                    snowflakeRole: PROD_SNOWFLAKE_ROLE,
                    snowflakeWarehouse: PROD_SNOWFLAKE_WAREHOUSE,
                    snowflakeSchema: PROD_SNOWFLAKE_SCHEMA,
                    snowflakePrivateKeySecretId: PROD_SNOWFLAKE_PRIVATE_KEY_SECRET_ID,
                    snowflakeKeyPassphraseSecretId: PROD_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID,
                )
            }
        }
        stage('Run PROD tests') {
            when {
                allOf {
                    branch 'master'
                    expression { return params.RUN_ON_PROD && params.RUN_PROD_TESTS || isScheduler }
                }
            }
            steps {
                script {
                    runTest(
                        deploymentRole: PROD_DEPLOYMENT_ROLE,
                        accountId: PROD_ACCOUNT_ID,
                        sessionName: PROD_SESSION_NAME,
                        region: AWS_REGIONS[0],
                        snowflakeUser: PROD_SNOWFLAKE_USER,
                        snowflakeRole: PROD_SNOWFLAKE_ROLE,
                        snowflakeWarehouse: PROD_SNOWFLAKE_WAREHOUSE,
                        snowflakeSchema: PROD_SNOWFLAKE_SCHEMA,
                        snowflakePrivateKeySecretId: PROD_SNOWFLAKE_PRIVATE_KEY_SECRET_ID,
                        snowflakeKeyPassphraseSecretId: PROD_SNOWFLAKE_KEY_PASSPHRASE_SECRET_ID,
                        dbtTarget: 'prod',
                    )
                }
            }
        }
    }

    post {
        regression {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
        fixed {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
    }
}
