import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateStageDocumentation
import com.sap.piper.JenkinsUtils
import com.sap.piper.StageNameProvider
import com.sap.piper.Utils
import com.sap.piper.k8s.ContainerMap
import groovy.transform.Field

import static com.sap.piper.Prerequisites.checkScript

@Field String STEP_NAME = getClass().getName()
@Field String TECHNICAL_STAGE_NAME = 'init'

@Field Set GENERAL_CONFIG_KEYS = [
    /**
     * Defines the build tool used.
     * @possibleValues `docker`, `kaniko`, `maven`, `mta, ``npm`
     */
    'buildTool',
    /**
     * Defines the library resource containing the container map.
     */
    'containerMapResource',
    /**
     * Enable automatic inference of build tool (maven, npm, mta) based on existing project files.
     * If this is set to true, it is not required to provide the `buildTool` parameter in the `general` section of the pipeline configuration.
     */
    'inferBuildTool',
    /**
     * Enables automatic inference from the build descriptor in case projectName is not configured.
     */
    'inferProjectName',
    /**
     * Defines the library resource containing the legacy configuration definition.
     */
    'legacyConfigSettings',
    /**
     * Defines the main branch for your pipeline. **Typically this is the `master` branch, which does not need to be set explicitly.** Only change this in exceptional cases to a fixed branch name.
     */
    'productiveBranch',
    /**
     * Name of the project, e.g. used for the name of lockable resources.
     */
    'projectName',
    /**
     * Specify to execute artifact versioning in a kubernetes pod.
     * @possibleValues `true`, `false`
     */
    'runArtifactVersioningOnPod',
    /**
     *  Defines the library resource containing stage/step initialization settings. Those define conditions when certain steps/stages will be activated. **Caution: changing the default will break the standard behavior of the pipeline - thus only relevant when including `Init` stage into custom pipelines!**
     */
    'stageConfigResource',
    /**
     * Defines the library resource containing the stash settings to be performed before and after each stage. **Caution: changing the default will break the standard behavior of the pipeline - thus only relevant when including `Init` stage into custom pipelines!**
     */
    'stashSettings',
    /**
    * Works as the stashSettings parameter, but allows the use of a stash settings file that is not available as a library resource.
    */
    'customStashSettings',
    /**
     * Whether verbose output should be produced.
     * @possibleValues `true`, `false`
     */
    'verbose'
]
@Field STAGE_STEP_KEYS = [
    /**
     * Sets the build version.
     * @possibleValues `true`, `false`
     */
    'artifactPrepareVersion',
    /**
     * Retrieve transport request from git commit history.
     * @possibleValues `true`, `false`
     */
    'transportRequestReqIDFromGit'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus(STAGE_STEP_KEYS)
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
    /**
     * Enables the use of technical stage names.
     */
    'useTechnicalStageNames',
    /**
     * Provides a clone from the specified repository.
     * This map contains attributes, such as, `branches`, `extensions`, `userRemoteConfigs` etc.
     * Example: `[$class: 'GitSCM', branches: [[name: <branch_to_be_cloned>]], userRemoteConfigs: [[credentialsId: <credential_to_access_repository>, url: <repository_url>]]]`.
     */
    'checkoutMap',
    /**
     * The map returned from a Jenkins git checkout. Used to set the git information in the
     * common pipeline environment.
     */
    'scmInfo',
    /**
     * Optional skip of checkout if checkout was done before this step already.
     * @possibleValues `true`, `false`
     */
    'skipCheckout',
    /**
    * Mandatory if you skip the checkout. Then you need to unstash your workspace to get the e.g. configuration.
    */
    'stashContent',
    /**
     * Optional path to the pipeline configuration file defining project specific settings.
     */
    'configFile',
    /**
     * Optional list of file names which will be extracted from library resources and which serve as source for
     * default values for the pipeline configuration. These are merged with and override built-in defaults, with
     * a parameter supplied by the last resource file taking precedence over the same parameter supplied in an
     * earlier resource file or built-in default.
     */
    'customDefaults',
    /**
     * Optional list of file paths or URLs which must point to YAML content. These work exactly like
     * `customDefaults`, but from local or remote files instead of library resources. They are merged with and
     * take precedence over `customDefaults`.
     */
    'customDefaultsFromFiles'
])

/**
 * This stage initializes the pipeline run and prepares further execution.
 *
 * It will check out your repository and perform some steps to initialize your pipeline run.
 */
@GenerateStageDocumentation(defaultStageName = 'Init')
void call(Map parameters = [:]) {
    def script = checkScript(this, parameters) ?: this
    def utils = parameters.juStabUtils ?: new Utils()

    if (parameters.useTechnicalStageNames) {
        StageNameProvider.instance.useTechnicalStageNames = true
    }

    def stageName = StageNameProvider.instance.getStageName(script, parameters, this)

    piperStageWrapper (script: script, stageName: stageName, stashContent: [], ordinal: 1, telemetryDisabled: true) {
        def skipCheckout = parameters.skipCheckout
        if (skipCheckout != null && !(skipCheckout instanceof Boolean)) {
            error "[${STEP_NAME}] Parameter skipCheckout has to be of type boolean. Instead got '${skipCheckout.class.getName()}'"
        }
        def scmInfo = parameters.scmInfo
        if (skipCheckout && !scmInfo) {
            error "[${STEP_NAME}] Need am scmInfo map retrieved from a checkout. " +
                "If you want to skip the checkout the scm info needs to be provided by you with parameter scmInfo, " +
                "for example as follows:\n" +
                "  def scmInfo = checkout scm\n" +
                "  piperPipelineStageInit script:this, skipCheckout: true, scmInfo: scmInfo"
        }
        if (!skipCheckout) {
            scmInfo = checkout(parameters.checkoutMap ?: scm)
        }
        else {
            def stashContent = parameters.stashContent
            if(stashContent == null || stashContent.size() == 0) {
                error "[${STEP_NAME}] needs stashes if you skip checkout"
            }
            utils.unstashAll(stashContent)
        }

        setupCommonPipelineEnvironment(script: script, customDefaults: parameters.customDefaults, scmInfo: scmInfo,
            configFile: parameters.configFile, customDefaultsFromFiles: parameters.customDefaultsFromFiles)

        Map config = ConfigurationHelper.newInstance(this)
            .loadStepDefaults()
            .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
            .mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
            .mixin(parameters, PARAMETER_KEYS)
            .addIfEmpty('stageConfigResource', 'com.sap.piper/pipeline/stageDefaults.yml')
            .addIfEmpty('stashSettings', 'com.sap.piper/pipeline/stashSettings.yml')
            .addIfEmpty('buildTool', script.commonPipelineEnvironment.buildTool)
            .withMandatoryProperty('buildTool')
            .use()


        if (config.legacyConfigSettings) {
            Map legacyConfigSettings = readYaml(text: libraryResource(config.legacyConfigSettings))
            checkForLegacyConfiguration(script: script, legacyConfigSettings: legacyConfigSettings)
        }

        String buildTool = config.buildTool
        String buildToolDesc = inferBuildToolDesc(script, config.buildTool)

        checkBuildTool(buildTool, buildToolDesc)

        script.commonPipelineEnvironment.projectName = config.projectName

        if (!script.commonPipelineEnvironment.projectName && config.inferProjectName) {
            script.commonPipelineEnvironment.projectName = inferProjectName(script, buildTool, buildToolDesc)
        }

        if (Boolean.valueOf(env.ON_K8S) && config.containerMapResource) {
            ContainerMap.instance.initFromResource(script, config.containerMapResource, buildTool)
        }

        initStashConfiguration(script, config.stashSettings, config.customStashSettings, config.verbose ?: false)

        if (config.verbose) {
            echo "piper-lib-os  configuration: ${script.commonPipelineEnvironment.configuration}"
        }

        // telemetry reporting
        utils.pushToSWA([step: STEP_NAME], config)

        piperInitRunStageConfiguration script: script, stageConfigResource: config.stageConfigResource

        // CHANGE_ID is set only for pull requests
        if (env.CHANGE_ID) {
            List prActions = []

            //get trigger action from comment like /piper action
            def jenkinsUtils = new JenkinsUtils()
            def commentTriggerAction = jenkinsUtils.getIssueCommentTriggerAction()

            if (commentTriggerAction != null) prActions.add(commentTriggerAction)

            try {
                prActions.addAll(pullRequest.getLabels().asList())
            } catch (ex) {
                echo "[${STEP_NAME}] GitHub labels could not be retrieved from Pull Request, please make sure that credentials are maintained on multi-branch job."
            }

            setPullRequestStageStepActivation(script, config, prActions)
        }

        if (env.BRANCH_NAME == config.productiveBranch) {
            if (parameters.script.commonPipelineEnvironment.configuration.runStep?.get('Init')?.slackSendNotification) {
                slackSendNotification script: script, message: "STARTED: Job <${env.BUILD_URL}|${URLDecoder.decode(env.JOB_NAME, java.nio.charset.StandardCharsets.UTF_8.name())} ${env.BUILD_DISPLAY_NAME}>", color: 'WARNING'
            }

            config.artifactPrepareVersion = true
        }

        if (config.artifactPrepareVersion) {
            Map prepareVersionParams = [script: script]
            if (config.inferBuildTool) {
                prepareVersionParams.buildTool = buildTool
            }
            if(buildToolDesc) {
                prepareVersionParams.filePath = buildToolDesc
            }
            if (env.ON_K8S && !config.runArtifactVersioningOnPod) {
                // We force dockerImage: "" for the K8S case to avoid the execution of artifactPrepareVersion in a K8S Pod.
                // Since artifactPrepareVersion may need the ".git" folder in order to push a tag, it would need to be part of the stashing.
                // There are however problems with tar-ing this folder, which results in a failure to copy the stash back -- without a failure of the pipeline.
                // This then also has the effect that any changes made to the build descriptors by the step (updated version) are not visible in the relevant stashes.
                // In addition, a mvn executable is available on the Jenkins instance which can be used directly instead of executing the command in a container.
                prepareVersionParams.dockerImage = ""
            }
            artifactPrepareVersion prepareVersionParams
        }

        if (config.transportRequestReqIDFromGit) {
            transportRequestReqIDFromGit(script: script)
        }
        pipelineStashFilesBeforeBuild script: script
    }
}

// Infer build tool descriptor (maven, npm, mta)
private static String inferBuildToolDesc(script, buildTool) {

    String buildToolDesc = null

    switch (buildTool) {
        case 'maven':
            Map configBuild = script.commonPipelineEnvironment.getStepConfiguration('mavenBuild', 'Build')
            buildToolDesc = configBuild.pomPath? configBuild.pomPath : 'pom.xml'
            break
        case 'npm': // no parameter for the descriptor path
            buildToolDesc = 'package.json'
            break
        case 'mta':
            Map configBuild = script.commonPipelineEnvironment.getStepConfiguration('mtaBuild', 'Build')
            buildToolDesc = configBuild.source? configBuild.source + '/mta.yaml' : 'mta.yaml'
            break
        default:
            break;
    }

    return buildToolDesc
}

private String inferProjectName(Script script, String buildTool, String buildToolDesc) {
    switch (buildTool) {
        case 'maven':
            def pom = script.readMavenPom file: buildToolDesc
            return "${pom.groupId}-${pom.artifactId}"
        case 'npm':
            Map packageJson = script.readJSON file: buildToolDesc
            return packageJson.name
        case 'mta':
            Map mta = script.readYaml file: buildToolDesc
            return mta.ID
    }

    script.error "Cannot infer projectName. Project buildTool was none of the expected ones 'mta', 'maven', or 'npm'."
}

private checkBuildTool(String buildTool, String buildDescriptorPattern) {
    if (buildDescriptorPattern && !findFiles(glob: buildDescriptorPattern)) {
        error "[${STEP_NAME}] buildTool configuration '${buildTool}' does not fit to your project (buildDescriptorPattern: '${buildDescriptorPattern}'), please set buildTool as general setting in your .pipeline/config.yml correctly, see also https://sap.github.io/jenkins-library/configuration/"
    }
}

private void initStashConfiguration (script, stashSettings, customStashSettings, verbose) {
    Map stashConfiguration = null
    if (customStashSettings){
        stashConfiguration = readYaml(file: customStashSettings)
    }else{
        stashConfiguration = readYaml(text: libraryResource(stashSettings))
    }
    if (verbose) echo "Stash config: ${stashConfiguration}"
    script.commonPipelineEnvironment.configuration.stageStashes = stashConfiguration
}

private void setPullRequestStageStepActivation(script, config, List actions) {

    if (script.commonPipelineEnvironment.configuration.runStep == null)
        script.commonPipelineEnvironment.configuration.runStep = [:]
    if (script.commonPipelineEnvironment.configuration.runStep[config.pullRequestStageName] == null)
        script.commonPipelineEnvironment.configuration.runStep[config.pullRequestStageName] = [:]

    actions.each {action ->
        if (action.startsWith(config.labelPrefix))
            action = action.minus(config.labelPrefix)

        def stepName = config.stepMappings[action]
        if (stepName) {
            script.commonPipelineEnvironment.configuration.runStep."${config.pullRequestStageName}"."${stepName}" = true
        }
    }
}
