String GITHUB_REPOSITORY = 'orchard'
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'
Integer CRON_UPDATE_TIMEOUT = 1800

List<String> VULNERABILITIES_TO_IGNORE = [
    'CVE-2026-3805',  // pkg:deb/debian/curl@8.14.1-2%2Bdeb13u3?arch=amd64&distro=trixie&epoch=0, no patch available
    'CVE-2025-13151' // pkg:deb/debian/libtasn1-6@4.20.0-2?arch=amd64&distro=trixie&epoch=0
]

PROD_CRON_SERVICES = [
    [serviceName: 'hfa-escrow-release', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'hfa-cron-invoice', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'hfa-cron-invoice-theorchard', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'hfa-cron-response', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'hfa-usage-reports', deployType: 'CREATE_TASK_DEFINITION'],
    [serviceName: 'cron-tourdate-reminder', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'cron-new-ppl', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'auto-encoding-order-upc', deployType: 'UPDATE_SERVICE', forceScaleOut: true, verifyMode: 'TASK_RUNNING'],
    [serviceName: 'error-correction-upc', deployType: 'UPDATE_SERVICE', forceScaleOut: true, verifyMode: 'TASK_RUNNING'],
    [serviceName: 'carveout-changed-upc', deployType: 'UPDATE_SERVICE', forceScaleOut: true, verifyMode: 'TASK_RUNNING'],
    [serviceName: 'generate-meta-update-eo', deployType: 'UPDATE_SERVICE'],
    [serviceName: 'auto-encoding-order-upc-fc', deployType: 'UPDATE_SERVICE', forceScaleOut: true, verifyMode: 'TASK_RUNNING'],
    [serviceName: 'carveout-changed-upc-fc', deployType: 'UPDATE_SERVICE', forceScaleOut: true, verifyMode: 'TASK_RUNNING'],
    [serviceName: 'generate-video-encoding-orders', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
    [serviceName: 'pop-encoding-order-upc', deployType: 'UPDATE_CLOUDWATCH_EVENT'],
]

ALL_SERVICES = PROD_CRON_SERVICES + [serviceName: 'oa']

QA_CRON_SERVICES = [
    [serviceName: 'generate-meta-update-eo', deployType: 'UPDATE_SERVICE'],
]

pipeline {
    agent any

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

    parameters {
        booleanParam(name: 'DEPLOY_TO_QA', defaultValue: true, description: 'Whether or not to deploy to QA.')
        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.')
        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('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 'docker compose down --remove-orphans'
                        sh 'cp .env.shadow .env'
                        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 --remove-orphans'
                }
                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/orchard.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/oa/timestamp"
                    withAWS(region: AWS_REGIONS[0], credentials: 'cdn-deploy') {
                        step([$class: 'hudson.plugins.s3.S3BucketPublisher',
                            entries: [
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/css",
                                sourceFile: 'public/css/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/fonts",
                                sourceFile: 'public/fonts/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared",
                                sourceFile: 'manifest',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/oa",
                                sourceFile: 'public/oa/timestamp',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/images",
                                sourceFile: 'public/images/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/oa/images",
                                sourceFile: 'public/oa/images/',
                                selectedRegion: 'us-east-1',
                                flatten: true],
                                [bucket: "qa-orcd-cdn/shared/${timestamp}/js",
                                sourceFile: 'public/js/',
                                selectedRegion: 'us-east-1',
                                flatten: true]
                            ]
                        ])
                    }
                }
            }
        }
        stage('QA Cloudfront CDN Cache Invalidation') {
            when {
                branch 'master'
            }
            steps {
                cdnInvalidate(
                    env: 'qa',
                    appName: 'shared'
                )
            }
        }
        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 'rm -rf build/logs'
                                sh 'cp .env.shadow .env'
                                sh 'docker compose down --remove-orphans'
                                sh 'mkdir -p build/logs'
                                sh 'chmod a+w build/logs'
                                sh 'docker compose run --rm --build unit-lint'
                            }
                        }
                    }
                    post {
                        always {
                            sh 'docker compose down --remove-orphans'
                            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('Validate Software Catalog Definitions') {
                    steps {
                        script {
                            parallel(ALL_SERVICES.collectEntries {service ->
                                [
                                    (service.serviceName): {
                                        echo "Validating Software Catalog definition for ${service.serviceName}"
                                        datadogSoftwareCatalogValidate(serviceDefinitionFilePath: "software_catalog/${service.serviceName}.yaml")
                                    }
                                ]
                            })
                        }
                    }
                }
            }
        }
        stage('Static Application Security Tests') {
            steps {
                sastTests(v2: true)
            }
        }
        stage ('Build Docker Images and Ship to ECR') {
            parallel {
                stage('QA/PROD Image') {
                    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: 'oa',
                                imageTag: env.GIT_COMMIT,
                                dockerBuildFile: 'Dockerfile',
                                dockerBuildSecrets: [[id: 'github_auth', env: 'GITHUB_AUTH']],
                                dockerBuildCacheDisabled: false,
                                dockerBuildTarget: 'deploy'
                        }
                    }
                }
                stage('Dev Image') {
                    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-orchard',
                                imageTag: 'debian',
                                dockerBuildFile: 'Dockerfile',
                                dockerBuildSecrets: [[id: 'github_auth', env: 'GITHUB_AUTH']],
                                dockerBuildCacheDisabled: false,
                                dockerBuildTarget: 'dev'
                        }
                    }
                }
                stage('Cron Worker Image') {
                    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: 'orchard-repo-cron-worker',
                                imageTag: env.GIT_COMMIT,
                                dockerBuildFile: 'Dockerfile',
                                dockerBuildSecrets: [[id: 'github_auth', env: 'GITHUB_AUTH']],
                                dockerBuildCacheDisabled: false,
                                dockerBuildTarget: 'cron'
                        }
                    }
                }
            }
        }
        stage('Deploy to QA') {
            parallel {
                stage('Deploy OA to QA') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_QA }
                        }
                    }
                    steps {
                        fargateDeploy environment: 'qa', awsRegions: AWS_REGIONS, gitCommit: env.GIT_COMMIT, serviceName: 'oa',
                            ecrRegistryAccountId: ECR_ACCOUNT_ID, awsDeploymentTargetAccountId: QA_ACCOUNT_ID, awsDeploymentRoleName: QA_DEPLOYMENT_ROLE
                    }
                }
                stage('Deploy Cron Workers to QA') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_QA }
                        }
                    }
                    steps {
                        script {
                            parallel(QA_CRON_SERVICES.collectEntries {service ->
                                [
                                    (service.serviceName): {
                                        fargateDeploy environment: 'qa',
                                            awsRegions: AWS_REGIONS,
                                            gitCommit: env.GIT_COMMIT,
                                            serviceName: service.serviceName,
                                            ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                            imageNameOverride: 'orchard-repo-cron-worker',
                                            verifyMode: 'EXIT_CODE',
                                            awsDeploymentTargetAccountId: QA_ACCOUNT_ID,
                                            awsDeploymentRoleName: QA_DEPLOYMENT_ROLE,
                                            deployType: service.deployType
                                    }
                                ]
                            })
                        }
                    }
                }
            }
        }
        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: 'oa', imageTag: env.GIT_COMMIT, vulnerabilitiesToIgnore: VULNERABILITIES_TO_IGNORE,
                    slackNotificationChannel: (env.BRANCH_NAME == 'master' ? SLACK_NOTIFICATIONS_CHANNEL : null)
            }
        }
        stage('Playwright E2E Tests') {
            when {
                allOf {
                    branch 'master'
                    expression { params.RUN_E2E_TESTS }
                }
            }
            environment {
                TAGS = "@oa"
            }
            steps {
                playwrightTests(tags: env.TAGS, envVars: ['USE_DYNAMIC_THROTTLING': 'true', MAX_CONCURRENT_FEATURES: '10'])
            }
        }

        stage('Deploy to Prod') {
            parallel {
                stage('Deploy OA to Prod') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        fargateDeploy environment: 'prod', awsRegions: AWS_REGIONS, gitCommit: env.GIT_COMMIT, serviceName: 'oa',
                            ecrRegistryAccountId: ECR_ACCOUNT_ID, awsDeploymentTargetAccountId: PROD_ACCOUNT_ID, awsDeploymentRoleName: PROD_DEPLOYMENT_ROLE
                    }
                }
                stage('Update Cron Fargate Eventbridge Events') {
                    when {
                        allOf {
                            branch 'master'
                            expression { params.DEPLOY_TO_PROD }
                        }
                    }
                    steps {
                        script {
                            parallel(PROD_CRON_SERVICES.collectEntries {service ->
                                [
                                    (service.serviceName): {
                                        fargateDeploy environment: 'prod',
                                            awsRegions: AWS_REGIONS,
                                            gitCommit: env.GIT_COMMIT,
                                            serviceName: service.serviceName,
                                            ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                            imageNameOverride: 'orchard-repo-cron-worker',
                                            verifyMode: service.verifyMode ?: 'EXIT_CODE',
                                            awsDeploymentTargetAccountId: PROD_ACCOUNT_ID,
                                            awsDeploymentRoleName: PROD_DEPLOYMENT_ROLE,
                                            deployType: service.deployType,
                                            forceScaleOut: service.forceScaleOut,
                                            updateTimeout: service.updateTimeout ?: 300
                                    }
                                ]
                            })
                        }
                    }
                }
                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/oa/timestamp"
                            withAWS(region: AWS_REGIONS[0], credentials: 'cdn-deploy') {
                                step([$class: 'hudson.plugins.s3.S3BucketPublisher',
                                    entries: [
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/css",
                                        sourceFile: 'public/css/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/fonts",
                                        sourceFile: 'public/fonts/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared",
                                        sourceFile: 'manifest',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/oa",
                                        sourceFile: 'public/oa/timestamp',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/images",
                                        sourceFile: 'public/images/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/oa/images",
                                        sourceFile: 'public/oa/images/',
                                        selectedRegion: 'us-east-1',
                                        flatten: true],
                                        [bucket: "prod-orcd-cdn/shared/${timestamp}/js",
                                        sourceFile: 'public/js/',
                                        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: 'shared'
                        )
                    }
                }
                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('Publish Software Catalog Definitions') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                script {
                    parallel(ALL_SERVICES.collectEntries {service ->
                        [
                            (service.serviceName): {
                                echo "Publishing Software Catalog definition for ${service.serviceName}"
                                datadogSoftwareCatalogPublish(serviceDefinitionFilePath: "software_catalog/${service.serviceName}.yaml")
                            }
                        ]
                    })
                }
            }
        }
    }

    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()
        }
    }
}
