import static com.sap.piper.Prerequisites.checkScript

import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import com.sap.piper.mta.MtaMultiplexer

import groovy.transform.Field

@Field def STEP_NAME = getClass().getName()

@Field Set GENERAL_CONFIG_KEYS = [
    /**
     * Credentials for accessing the Snyk API.
     * @possibleValues Jenkins credentials id
     */
    'snykCredentialsId'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
    /**
     * The path to the build descriptor file, e.g. `./package.json`.
     */
    'buildDescriptorFile',
    /** @see dockerExecute */
    'dockerImage',
    /**
     * Only scanType 'mta': Exclude modules from MTA projects.
     */
    'exclude',
    /**
     * Monitor the application's dependencies for new vulnerabilities.
     */
    'monitor',
    //TODO: move to general
    /**
     * The type of project that should be scanned.
     * @possibleValues `npm`, `mta`
     */
    'scanType',
    /**
     * Only needed for `monitor: true`: The organisation ID to determine the organisation to report to.
     */
    'snykOrg',
    /**
     * Generate and archive a JSON report.
     */
    'toJson',
    /**
     * Generate and archive a HTML report.
     */
    'toHtml'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS

//https://snyk.io/docs/continuous-integration/
/**
 * This step performs an open source vulnerability scan on a *Node project* or *Node module inside an MTA project* through snyk.io.
 */
@GenerateDocumentation
void call(Map parameters = [:]) {
    handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
        def utils = parameters.juStabUtils ?: new Utils()

        def script = checkScript(this, parameters) ?: this

        Map config = ConfigurationHelper.newInstance(this)
            .loadStepDefaults()
            .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
            .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
            .mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
            .mixin(parameters, PARAMETER_KEYS)
            // check mandatory paramerers
            .withMandatoryProperty('dockerImage')
            .withMandatoryProperty('snykCredentialsId')
            .use()

        new Utils().pushToSWA([
            step: STEP_NAME,
            stepParamKey1: 'scriptMissing',
            stepParam1: parameters?.script == null
        ], config)

        utils.unstashAll(config.stashContent)

        switch(config.scanType) {
            case 'mta':
                def scanJobs = [failFast: false]
                // create job for each package.json with scanType: 'npm'
                scanJobs.putAll(MtaMultiplexer.createJobs(
                    this, parameters, config.exclude, 'Snyk', 'package.json', 'npm'
                ){options -> snykExecute(options)})
                // execute scan jobs in parallel
                parallel scanJobs
                break
            case 'npm':
                // set default file for scanType
                def path = config.buildDescriptorFile.replace('package.json', '')
                try{
                    withCredentials([string(
                        credentialsId: config.snykCredentialsId,
                        variable: 'token'
                    )]) {
                        dockerExecute(
                            script: script,
                            dockerImage: config.dockerImage,
                            stashContent: config.stashContent,
                            dockerEnvVars: ['SNYK_TOKEN': token]
                        ) {
                            // install Snyk
                            sh 'npm install snyk --global --quiet'
                            if(config.toHtml){
                                config.toJson = true
                                sh 'npm install snyk-to-html --global --quiet'
                            }
                            // install NPM dependencies
                            sh "cd '${path}' && npm install --quiet"
                            // execute Snyk scan
                            def cmd = []
                            cmd.push("cd '${path}'")
                            if(config.monitor) {
                                cmd.push('&& snyk monitor')
                                if(config.snykOrg)
                                    cmd.push("--org=${config.snykOrg}")
                            }
                            cmd.push('&& snyk test')
                            if(config.toJson)
                                cmd.push("--json > snyk.json")
                            try{
                                sh cmd.join(' ')
                            }finally{
                                if(config.toHtml) sh "snyk-to-html -i ${path}snyk.json -o ${path}snyk.html"
                            }
                        }
                    }
                }finally{
                    if(config.toJson) archiveArtifacts "${path.replaceAll('\\./', '')}snyk.json"
                    if(config.toHtml) archiveArtifacts "${path.replaceAll('\\./', '')}snyk.html"
                }
                break
            default:
                error "[ERROR][${STEP_NAME}] ScanType '${config.scanType}' not supported!"
        }
    }
}
