String GITHUB_REPOSITORY = 'workstation'
String ECR_ACCOUNT_ID = '086679231553'
List<String> AWS_REGIONS = ['us-east-1']
String SLACK_NOTIFICATIONS_CHANNEL = '#distro-build-alerts'
String QA_ACCOUNT_ID = '437795906767'
String QA_DEPLOYMENT_ROLE = 'prod-jenkins-aws-pipeline-agent'
String PROD_ACCOUNT_ID = '437795906767'
String PROD_DEPLOYMENT_ROLE = 'prod-jenkins-aws-pipeline-agent'
String BUILD_ROLE = 'prod-oa-ws-pipeline-role'
String QA_S3_CDN_BUCKET = 'qa-orcd-cdn'
String PROD_S3_CDN_BUCKET = 'prod-orcd-cdn'

List<String> VULNERABILITIES_TO_IGNORE = [
    'CVE-2021-35515',
    'CVE-2021-35516',
    'CVE-2021-36090',
    'CVE-2021-35517',
    'CVE-2021-23358',
    'CVE-2025-64500', // https://nvd.nist.gov/vuln/detail/CVE-2025-64500
    'CVE-2026-23876',
    'CVE-2025-45769',
    'CVE-2026-27601',
    'CVE-2022-24894',
    'CVE-2026-27622', // openexr
    'CVE-2026-34543', // openexr
    'CVE-2026-34544', // openexr
    'CVE-2026-34545', // openexr
    'CVE-2026-34379', // openexr
    'CVE-2026-34588', // openexr
    'CVE-2026-34589', // openexr
]

pipeline {
    agent any

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

    parameters {
        booleanParam(name: 'DEPLOY_TO_PROD', defaultValue: false, 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.')
        booleanParam(name: 'RUN_E2E_TESTS', defaultValue: true, description: 'Whether or not to run e2e tests.')
    }

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

    stages {
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }
        stage('Compliance Checks') {
            steps {
                complianceChecks()
            }
        }
        stage('Validate Software Catalog Definition') {
            steps {
                datadogSoftwareCatalogValidate()
            }
        }
        stage('Build Static Files') {
            when {
                branch 'master'
            }
            steps {
                withSecrets(awsAccountId: PROD_ACCOUNT_ID, awsRole: BUILD_ROLE, secrets: [
                    [id: 'prod/oa-ws-pipeline/COMPOSER_GITHUB_AUTH', environmentVariable: 'GITHUB_AUTH']
                ]) {
                    withEcr {
                        sh 'JENKINS_UID=$(id -u) JENKINS_GID=$(id -g) docker compose up --exit-code-from build-static-files --abort-on-container-exit --build build-static-files'
                    }
                }
            }
            post {
                always {
                    sh 'docker compose down'
                }
                success {
                     sh("""
                        #!/usr/bin/env bash
                        git update-index --refresh
                        if git diff-index --quiet HEAD --; then
                            echo "No changes to commit."
                        else
                            git commit -m '[Deploy] built files (js / css)'
                            git remote set-url origin git@github.com:theorchard/workstation.git
                            git push origin HEAD:master
                        fi
                     """)
                }
            }
        }
        stage('Deploy Static Files to QA') {
            when {
                branch 'master'
            }
            steps {
                script {
                    def timestamp = new Date().format("yyyyMMddHHmmss")
                    sh "echo ${timestamp} > manifest"
                    sh "echo ${timestamp} > public/alw/timestamp"
                    withAWS(region: AWS_REGIONS[0], credentials: 'cdn-deploy') {
                        step([$class: 'hudson.plugins.s3.S3BucketPublisher',
                            entries: [
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/css",
                                sourceFile: 'public/css/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/fonts",
                                sourceFile: 'public/fonts/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/images",
                                sourceFile: 'public/images/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/alw/images",
                                sourceFile: 'public/alw/images/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/js",
                                sourceFile: 'public/js/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation",
                                sourceFile: 'manifest',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/alw",
                                sourceFile: 'public/alw/timestamp',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts",
                                sourceFile: 'public/scripts/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/alw/scripts",
                                sourceFile: 'public/alw/scripts/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/themes/simple",
                                sourceFile: 'public/scripts/tiny_mce/themes/simple/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced",
                                sourceFile: 'public/scripts/tiny_mce/themes/advanced/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/langs",
                                sourceFile: 'public/scripts/tiny_mce/langs/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/langs",
                                sourceFile: 'public/scripts/tiny_mce/themes/advanced/langs/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/skins/o2k7",
                                sourceFile: 'public/scripts/tiny_mce/themes/advanced/skins/o2k7/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/img",
                                sourceFile: 'public/scripts/tiny_mce/themes/advanced/img/',
                                selectedRegion: 'us-east-1',
                                flatten: true]
                            ]
                        ])
                    }
                }
            }
        }
        stage('QA Cloudfront CDN Cache Invalidation') {
            when {
                branch 'master'
            }
            steps {
                cdnInvalidate(
                    env: 'qa',
                    appName: GITHUB_REPOSITORY
                )
            }
        }
        stage ('Run Unit Tests and Static Code Analysis') {
            parallel {
                stage('Unit tests and Style Checks') {
                    environment {
                        BUILD_TYPE = "${env.BRANCH_NAME == 'master' ? 'build-jenkins' : 'build-pr'}"
                    }
                    steps {
                        withSecrets(awsAccountId: PROD_ACCOUNT_ID, awsRole: BUILD_ROLE, secrets: [
                            [id: 'prod/oa-ws-pipeline/COMPOSER_GITHUB_AUTH', environmentVariable: 'GITHUB_AUTH'],
                        ]) {
                            withEcr {
                                sh 'cp .env.shadow .env'
                                sh 'make ci_unit_lint'
                            }
                        }
                    }
                    post {
                        always {
                            sh 'docker compose down'
                            xunit tools: [PHPUnit(pattern: 'build/logs/phpunit.xml', deleteOutputFiles: true, failIfNotNew: true, stopProcessingIfError: true)]
                            clover cloverReportDir: 'build/logs', cloverReportFileName: 'phpunit.coverage.xml',
                                healthyTarget: [methodCoverage: 70, conditionalCoverage: 80, statementCoverage: 80],
                                unhealthyTarget: [methodCoverage: 0, conditionalCoverage: 0, statementCoverage: 0],
                                failingTarget: [methodCoverage: 0, conditionalCoverage: 0, statementCoverage: 0]
                        }
                    }
                }
                stage('Sonar Scan and Analysis') {
                    when {
                        branch 'master'
                    }
                    steps {
                        sonarScan project: GITHUB_REPOSITORY, language: 'php', qualityGateTimeout: 180
                    }
                }  
            }
        }
        stage('Static Application Security Tests') {
            steps {
                sastTests(v2: true)
            }
        }
        stage ('Build QA Docker Image and Ship to ECR') {
            parallel {
                stage('Build a Docker Image and Ship to ECR') {
                    when {
                        anyOf {
                            branch 'master'
                            expression { env.GITHUB_COMMENT =~ 'build docker' }
                        }
                    }
                    steps {
                        withSecrets(awsAccountId: PROD_ACCOUNT_ID, awsRole: BUILD_ROLE, secrets: [
                            [id: 'prod/oa-ws-pipeline/COMPOSER_GITHUB_AUTH', environmentVariable: 'GITHUB_AUTH'],
                        ]) {
                            dockerToEcr awsRegions: AWS_REGIONS,
                                ecrAccountId: ECR_ACCOUNT_ID,
                                imageName: 'workstation',
                                imageTag: env.GIT_COMMIT,
                                dockerBuildFile: 'Dockerfile',
                                dockerBuildSecrets: [[id: 'github_auth', env: 'GITHUB_AUTH']],
                                dockerBuildCacheDisabled: false,
                                dockerBuildTarget: 'deploy'
                        }
                    }
                }
                stage('Build a Docker Dev Image and Ship to Dev ECR') {
                    when {
                        anyOf {
                            branch 'master'
                            expression { env.GITHUB_COMMENT =~ 'build docker' }
                        }
                    }
                    steps {
                        withSecrets(awsAccountId: PROD_ACCOUNT_ID, awsRole: BUILD_ROLE, secrets: [
                            [id: 'prod/oa-ws-pipeline/COMPOSER_GITHUB_AUTH', environmentVariable: 'GITHUB_AUTH'],
                        ]) {
                            dockerToEcr awsRegions: AWS_REGIONS,
                                ecrAccountId: '103233932089',
                                imageName: 'dev-workstation',
                                imageTag: 'latest',
                                dockerBuildFile: 'Dockerfile',
                                dockerBuildSecrets: [[id: 'github_auth', env: 'GITHUB_AUTH']],
                                dockerBuildCacheDisabled: false,
                                dockerBuildTarget: 'dev'
                        }
                    }
                }
            }
        }
        stage('Deploy Workstation to QA') {
            when {
                branch 'master'
            }
            steps {
                fargateDeploy environment: 'qa', awsRegions: AWS_REGIONS, gitCommit: env.GIT_COMMIT, serviceName: 'workstation',
                    ecrRegistryAccountId: ECR_ACCOUNT_ID, awsDeploymentTargetAccountId: QA_ACCOUNT_ID, awsDeploymentRoleName: QA_DEPLOYMENT_ROLE
            }
        }
        stage('Scan Docker Image') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                }
            }
            steps {
                dockerScan awsRegion: AWS_REGIONS[0], ecrAccountId: ECR_ACCOUNT_ID,
                    imageName: 'workstation', imageTag: env.GIT_COMMIT, vulnerabilitiesToIgnore: VULNERABILITIES_TO_IGNORE,
                    slackNotificationChannel: (env.BRANCH_NAME == 'master' ? SLACK_NOTIFICATIONS_CHANNEL : null)
            }
        }
        stage('Workstation E2E Tests') {
            when {
                allOf {
                    branch 'master'
                    expression { params.RUN_E2E_TESTS }
                }
            }
            environment {
                TAGS = "@legacy_workstation"
            }
            steps {
                playwrightTests tags: env.TAGS
            }
        }
        stage('Deploy to Prod') {
            parallel {
                stage('Deploy Workstation to Prod') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        fargateDeploy environment: 'prod', awsRegions: AWS_REGIONS, gitCommit: env.GIT_COMMIT, serviceName: 'workstation',
                            ecrRegistryAccountId: ECR_ACCOUNT_ID, awsDeploymentTargetAccountId: PROD_ACCOUNT_ID, awsDeploymentRoleName: PROD_DEPLOYMENT_ROLE
                        datadogSoftwareCatalogPublish()
                    }
                }
                stage('Deploy Static Files to Prod') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        script {
                            def timestamp = new Date().format("yyyyMMddHHmmss")
                            sh "echo ${timestamp} > manifest"
                            sh "echo ${timestamp} > public/alw/timestamp"
                            withAWS(region: AWS_REGIONS[0], credentials: 'cdn-deploy') {
                                step([$class: 'hudson.plugins.s3.S3BucketPublisher',
                                    entries: [
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/css",
                                        sourceFile: 'public/css/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/fonts",
                                        sourceFile: 'public/fonts/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/images",
                                        sourceFile: 'public/images/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/alw/images",
                                        sourceFile: 'public/alw/images/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/js",
                                        sourceFile: 'public/js/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation",
                                        sourceFile: 'manifest',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/alw",
                                        sourceFile: 'public/alw/timestamp',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts",
                                        sourceFile: 'public/scripts/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/alw/scripts",
                                        sourceFile: 'public/alw/scripts/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/themes/simple",
                                        sourceFile: 'public/scripts/tiny_mce/themes/simple/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced",
                                        sourceFile: 'public/scripts/tiny_mce/themes/advanced/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/langs",
                                        sourceFile: 'public/scripts/tiny_mce/langs/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/langs",
                                        sourceFile: 'public/scripts/tiny_mce/themes/advanced/langs/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/skins/o2k7",
                                        sourceFile: 'public/scripts/tiny_mce/themes/advanced/skins/o2k7/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/workstation/${timestamp}/scripts/themes/advanced/img",
                                        sourceFile: 'public/scripts/tiny_mce/themes/advanced/img/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true]
                                    ]
                                ])
                            }
                        }  
                    }
                }
            }
        }
        stage('Invalidate Prod CDN Caches') {
            parallel {
                stage('Prod Cloudfront CDN Cache Invalidation') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        cdnInvalidate(
                            env: 'prod',
                            appName: GITHUB_REPOSITORY
                        )
                    }
                }
                stage('Prod Cloudflare CDN Cache Invalidation') {
                    environment {
                        CLOUDFLARE_ZONE_ID = "8933e75491d1c763349f8c515ba14af1"
                        CLOUDFLARE_EMAIL = "rdemoose@theorchard.com" //todo: avoid using personal token
                    }
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        script {
                            withSecrets(awsAccountId: PROD_ACCOUNT_ID, awsRole: BUILD_ROLE, secrets: [
                                [id: 'prod/oa-ws-pipeline/CLOUDFLARE_API_TOKEN', environmentVariable: 'CLOUDFLARE_API_TOKEN'],
                            ]) {
                                def zoneId = env.CLOUDFLARE_ZONE_ID
                                def email = env.CLOUDFLARE_EMAIL
                                def url = "https://api.cloudflare.com/client/v4/zones/$zoneId/purge_cache"

                                def payload = '{"purge_everything":true}'

                                def response = sh(script: "curl -X POST $url -H 'X-Auth-Email: $email' -H \"X-Auth-Key: \$CLOUDFLARE_API_TOKEN\" -H 'Content-Type: application/json' --data '$payload'", returnStdout: true)

                                echo "Cloudflare CDN cache invalidation response: $response"
                            }
                        }
                    }
                }
            }
        }
        stage('Workstation Production E2E Tests') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                playwrightTests(configEnvironment: "prod", tags: "@workstation_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
                }
            }
        }
        cleanup {
            cleanWs()
        }
    }
}
