import com.sap.piper.ConfigurationHelper
import com.sap.piper.DownloadCacheUtils
import com.sap.piper.GenerateDocumentation
import com.sap.piper.k8s.ContainerMap
import groovy.transform.Field
import com.sap.piper.Utils

import static com.sap.piper.Prerequisites.checkScript

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

@Field Set GENERAL_CONFIG_KEYS = [
    /** Executes the deployments in parallel.*/
    'parallelExecution',
    /**
     * The branch used as productive branch, defaults to master.
     */
    'productiveBranch'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
    /**
     * The URLs under which the app is available after deployment.
     * Each element of appUrls must be a map containing a property url, an optional property credentialId, and an optional property parameters.
     * The optional property parameters can be used to pass additional parameters to the end-to-end test deployment reachable via the given application URL.
     * These parameters must be a list of strings, where each string corresponds to one element of the parameters.
     * For example, if the parameter `--tag scenario1` should be passed to the test, specify parameters: ["--tag", "scenario1"].
     * These parameters are appended to the npm command during execution.
     */
    'appUrls',
    /**
     * List of build descriptors and therefore modules to exclude from execution of the npm scripts.
     * The elements of the list can either be a path to the build descriptor or a pattern.
     */
    'buildDescriptorExcludeList',
    /**
     * Script to be executed from package.json. Defaults to `ci-e2e`.
     */
    'runScript',
    /**
     * Boolean to indicate whether the step should only be executed in the productive branch or not.
     * @possibleValues `true`, `false`
     */
    'onlyRunInProductiveBranch',
    /**
     * Docker image on which end to end tests should be executed
     */
    'dockerImage',
    /**
     * Base URL of the application to be tested
     */
    'baseUrl',
    /**
     * Credentials to access the application to be tested
     */
    'credentialsId'

])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS

@Field Map CONFIG_KEY_COMPATIBILITY = [parallelExecution: 'features/parallelTestExecution']

/**
 * Executes end to end tests by running the npm script configured via the `runScript` property.
 */
@GenerateDocumentation
void call(Map parameters = [:]) {
    handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
        def script = checkScript(this, parameters) ?: this
        String stageName = parameters.stageName ?: env.STAGE_NAME

        Map config = ConfigurationHelper.newInstance(this)
            .loadStepDefaults([:], stageName)
            .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
            .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
            .mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
            .mixin(parameters, PARAMETER_KEYS)
            .use()

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

        def e2ETests = [:]
        def index = 1

        def npmParameters = [:]
        npmParameters.dockerOptions = ['--shm-size 512MB']

        if (config.appUrls && !(config.appUrls instanceof List)) {
            error "[${STEP_NAME}] The execution failed, since appUrls is not a list. Please provide appUrls as a list of maps. For example:\n" +
                    "appUrls: \n" + "  - url: 'https://my-url.com'\n" + "    credentialId: myCreds"

        }

        if (config.onlyRunInProductiveBranch && (config.productiveBranch != env.BRANCH_NAME)) {
            return
        }
        List credentials = []

        if (config.appUrls){
            for (int i = 0; i < config.appUrls.size(); i++) {
                def appUrl = config.appUrls[i]
                if (!(appUrl instanceof Map)) {
                    error "[${STEP_NAME}] The element ${appUrl} is not of type map. Please provide appUrls as a list of maps. For example:\n" +
                            "appUrls: \n" + "  - url: 'https://my-url.com'\n" + "    credentialId: myCreds"
                }
                if (!appUrl.url) {
                    error "[${STEP_NAME}] No url property was defined for the following element in appUrls: ${appUrl}"
                }
                if (appUrl.credentialId) {
                    credentials.add(usernamePassword(credentialsId: appUrl.credentialId, passwordVariable: 'e2e_password', usernameVariable: 'e2e_username'))
                }
                List scriptOptions = ["--launchUrl=${appUrl.url}"]
                if (appUrl.parameters) {
                    if (appUrl.parameters instanceof List) {
                        scriptOptions = scriptOptions + appUrl.parameters
                    } else {
                        error "[${STEP_NAME}] The parameters property is not of type list. Please provide parameters as a list of strings."
                    }
                }
                e2ETests = runTest(scriptOptions, credentials, e2ETests, index, script, config, npmParameters, stageName)
                index++
            }
        }else{
            if (config.credentialsId) {
                credentials.add(usernamePassword(credentialsId: config.credentialsId, passwordVariable: 'e2e_password', usernameVariable: 'e2e_username'))
            }
            List scriptOptions = []
            if (config.baseUrl){
                scriptOptions = ["--baseUrl=${config.baseUrl}"]
            }
            e2ETests = runTest(scriptOptions, credentials, e2ETests, index, script, config, npmParameters, stageName)
        }
        runClosures(script, e2ETests, config.parallelExecution, "end to end tests")
    }
}

def runTest(scriptOptions, credentials, e2ETests, index, script, config, npmParameters, stageName){
    Closure e2eTest = {
        Utils utils = new Utils()
        utils.unstashStageFiles(script, stageName)
        try {
            withCredentials(credentials) {
                npmExecuteScripts(script: script, parameters: npmParameters, virtualFrameBuffer: true, runScripts: [config.runScript], dockerImage: config.dockerImage, scriptOptions: scriptOptions, buildDescriptorExcludeList: config.buildDescriptorExcludeList)
            }

        } catch (Exception e) {
            error "[${STEP_NAME}] The execution failed with error: ${e.getMessage()}"
        } finally {
            List cucumberFiles = findFiles(glob: "**/e2e/*.json")
            List junitFiles = findFiles(glob: "**/e2e/*.xml")

            if (cucumberFiles.size() > 0) {
                testsPublishResults script: script, cucumber: [active: true, archive: true]
            } else if (junitFiles.size() > 0) {
                testsPublishResults script: script, junit: [active: true, archive: true]
            } else {
                echo "[${STEP_NAME}] No JUnit or cucumber report files found, skipping report visualization."
            }

            utils.stashStageFiles(script, stageName)
        }
    }
    e2ETests["E2E Tests ${index > 1 ? index : ''}"] = {
        if (env.POD_NAME || env.ON_K8S) {
            dockerExecuteOnKubernetes(script: script, containerMap: ContainerMap.instance.getMap().get(stageName) ?: [:]) {
                e2eTest.call()
            }
        } else {
            node(env.NODE_NAME) {
                e2eTest.call()
            }
        }
    }
    return e2ETests
}
