String APP_NAME = 'auth0-hosted-pages'
String SLACK_NOTIFY_CHANNEL = '#auth0'
int MAX_RETRIES = 3
int RETRY_DELAY = 10  // seconds

def deployToEnv(Map params) {
    def appName = params.appName
    def envName = params.envName
    def envToAccountIdMap = [
        'dev': '103233932089',
        'qa': '437795906767',
        'prod': '437795906767',
    ]

    if(!envToAccountIdMap.containsKey(envName)) {
        error "Unknown env ${envName}"
    }

    def secrets = getSecrets(path: 'config/secret_names.txt', envName: envName, appName: appName)
    def envVars = getEnvVars(path: "config/${envName}.env")

    def role = "${envName}-${appName}-deploy-role"
    def accountId = envToAccountIdMap[envName]

    withSecrets(secrets: secrets.secretsList, awsAccountId: accountId, awsRole: role) {
        withEnv(envVars.varsList) {
            pnpmRun(
                scriptNames: [
                    'clean',
                    "pre-build ${envName}",
                    'build',
                    "run deploy ${envName}"
                ],
                envVars: secrets.envMap + envVars.envMap + [GIT_COMMIT: env.GIT_COMMIT]
            )
        }
    }
}

def runIntegrationTests(Map params) {
    def appName = params.appName
    def envName = params.envName
    def envToAccountIdMap = [
        'dev': '103233932089',
        'qa': '437795906767',
        'prod': '437795906767',
    ]
    def region = 'us-east-1'

    if(!envToAccountIdMap.containsKey(envName)) {
        error "Unknown env ${envName}"
    }

    def secrets = getSecrets(path: 'config/test_secret_names.txt', envName: envName, appName: appName)

    def role = "${envName}-${appName}-integration-test-role"
    def accountId = envToAccountIdMap[envName]
    def envVariables = [
        AWS_DEFAULT_REGION: "\${AWS_DEFAULT_REGION}",
        AWS_REGION: "\${AWS_REGION}",
        AWS_ACCESS_KEY_ID: "\${AWS_ACCESS_KEY_ID}",
        AWS_SECRET_ACCESS_KEY: "\${AWS_SECRET_ACCESS_KEY}",
        AWS_SESSION_TOKEN: "\${AWS_SESSION_TOKEN}",
    ]

    // change role
    withAWS(roleAccount: accountId, role: role, roleSessionName: env.BUILD_TAG, region: 'us-east-1', useNode: true) {
        // get secrets
        withSecrets(secrets: secrets.secretsList) {
            // run tests in docker
            pnpmRun(
                scriptNames: ['clean', 'test:integration'],
                envVars: secrets.envMap + envVariables
            )
        }
    }
}

def getSecrets(Map params) {
    def lines = getLines(path: params.path)
    def secretsList = []
    def envMap = [:]
    lines.each {
        def secretPath = it.contains('/')
            ? "${params.envName}/${it}"
            : "${params.envName}/${params.appName}/${it}"
        def envVarName = it.contains('/')
            ? it.replaceAll('[/\\-]', '_').toUpperCase()
            : it
        secretsList.add([id: secretPath, environmentVariable: envVarName])
        envMap.put((envVarName), "\$${envVarName}")
    }
    return [secretsList: secretsList, envMap: envMap]
}

def getEnvVars(Map params) {
    def lines = getLines(path: params.path)
    def varsList = []
    def envMap = [:]
    lines.each {
        def (key, value) = it.split('=', 2)
        def strippedKey = key.trim()
        varsList.add(it)
        envMap.put((strippedKey), "\$${strippedKey}")
    }
    return [varsList: varsList, envMap: envMap]
}

def getLines(Map params) {
    def path = params.path
    if (!fileExists(path)) {
        error "File not found: ${path}"
    }
    return readFile(path).split('\n').findAll{ it.trim() && !it.startsWith('#') }.collect { it.trim() }
}

pipeline {
    agent {
        label 'aws'
    }

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

    parameters {
        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.*|.*deploy dev.*|.*run integration tests.*')
    }

    stages {
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }

        stage('Compliance Checks') {
            when {
                not {
                    anyOf {
                        environment name: 'GITHUB_COMMENT', value: 'deploy dev'
                        environment name: 'GITHUB_COMMENT', value: 'run integration tests'
                    }
                }
            }
            steps {
                complianceChecks()
            }
        }

        stage('Unit tests and Style Checks') {
            when {
                not {
                    anyOf {
                        environment name: 'GITHUB_COMMENT', value: 'deploy dev'
                        environment name: 'GITHUB_COMMENT', value: 'run integration tests'
                    }
                }
            }
            steps {
                pnpmRun(scriptNames: ['clean', 'typecheck', 'test'])
            }
            post {
                always {
                    junit testResults: 'reports/junit/*.xml', allowEmptyResults: true
                }
            }
        }

        stage('Deploy to DEV') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.CHANGE_ID && env.GITHUB_COMMENT == 'deploy dev' }
                }
            }
            steps {
                deployToEnv(envName: 'dev', appName: APP_NAME)
            }
        }

        stage('Deploy to QA') {
            when {
                branch 'master'
            }
            steps {
                deployToEnv(envName: 'qa', appName: APP_NAME)
            }
        }

        stage('Integration Tests') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.CHANGE_ID && env.GITHUB_COMMENT == 'run integration tests' }
                }
            }
            steps {
                withBackoff(attempts: MAX_RETRIES, delay: RETRY_DELAY) {
                    runIntegrationTests(envName: 'qa', appName: APP_NAME)
                }
            }
        }

        stage('Playwright E2E Tests') {
            when {
                branch 'master'
            }
            steps {
                withBackoff(attempts: MAX_RETRIES, delay: RETRY_DELAY) {
                    playwrightTests(tags: '@frontend_auth0')
                }
            }
        }

        stage('Mobile Integration Tests') {
            when {
                branch 'master'
            }
            failFast true
            parallel {
                stage('Mobile App ios E2E Tests') {
                    steps {
                        withBackoff(attempts: MAX_RETRIES, delay: RETRY_DELAY) {
                            build job: 'mobile-e2e-tests', parameters: [
                                [$class: 'StringParameterValue', name: 'OS', value: 'ios'],
                                [$class: 'StringParameterValue', name: 'BRAND', value: 'orchard'],
                                [$class: 'StringParameterValue', name: 'TAGS', value: "@${APP_NAME}"]
                            ]
                        }
                    }
                }

                stage('Mobile App android E2E Tests') {
                    steps {
                        withBackoff(attempts: MAX_RETRIES, delay: RETRY_DELAY) {
                            build job: 'mobile-e2e-tests', parameters: [
                                [$class: 'StringParameterValue', name: 'OS', value: 'android'],
                                [$class: 'StringParameterValue', name: 'BRAND', value: 'orchard'],
                                [$class: 'StringParameterValue', name: 'OS_VERSION', value: '14.0'],
                                [$class: 'StringParameterValue', name: 'APPIUM_DEVICE', value: 'Google Pixel 8'],
                                [$class: 'StringParameterValue', name: 'TAGS', value: "@${APP_NAME}"]
                            ]
                        }
                    }
                }
            }
        }

        stage('Deploy to PROD') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                deployToEnv(envName: 'prod', appName: APP_NAME)
            }
        }
    }
    post {
        failure {
            cleanWs()

            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFY_CHANNEL
                }
            }
        }

        success {
            cleanWs()

            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFY_CHANNEL
                }
            }
        }
    }
}
