/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Release
 * (c) Copyright IBM Corporation 2017. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */
package com.urbancode.air.plugin.automation

import java.text.SimpleDateFormat

import com.urbancode.air.plugin.hp.alm.rest.ALMRestHelper
import com.urbancode.commons.util.query.QueryFilter.FilterType
import com.urbancode.release.rest.framework.QueryParams.FilterClass
import com.urbancode.release.rest.framework.Clients
import com.urbancode.release.rest.models.Application
import com.urbancode.release.rest.models.Change
import com.urbancode.release.rest.models.ChangeType
import com.urbancode.release.rest.models.Change.Status
import com.urbancode.release.rest.models.Change.Severity
import com.urbancode.release.rest.models.Initiative
import com.urbancode.release.rest.models.internal.PluginIntegrationProvider
import com.urbancode.release.rest.models.Release

class ALMIntegrationHelper extends ALMHelper {
    UCRClient ucrClient
    def mappingReleases
    def mappingReqStatuses
    def mappingReqSeverities
    def mappingDefectStatuses
    def mappingDefectSeverities
    def mappingTypes
    def autoMapReleases
    def appId   // changes share an application per integration run

    // change lists to be updated or created
    def createChangeList = [] as List
    def updateChangeList = [] as List

    public ALMIntegrationHelper(def props) {
        super(props)

        ucrClient = new UCRClient(props)

        // mappings
        mappingReleases = props['mappingReleases']
        mappingReqStatuses = props['mappingReqStatuses']
        mappingReqSeverities = props['mappingReqSeverities']
        mappingDefectStatuses = props['mappingDefectStatuses']
        mappingDefectSeverities = props['mappingDefectSeverities']
        mappingTypes = props['mappingTypes']
        autoMapReleases = Boolean.valueOf(props['almAutoMapReleases'])
    }

    // run integration and return exit value
    private void runIntegration() {
        ucrClient.init()
        def integrationProvider = ucrClient.getIntegrationProvider()
        def requirements // map of requirements with properties in the form 'name:value'
        def defects // map of defects with properties on the form 'name:value'

        // requirement type ids used to create change types on the ucr server using an id
        def reqTypeIds = getTypesMap("requirement")


        // requirements will be skipped if only defects are being pulled
        requirements = getRequirements()

        for (def requirement : requirements) {
            // get values for each required field
            def id = "Requirement " + requirement.id   // prepend Requirement to differentiate requirement ids from defect ids
            def name = requirement.get("name")
            def releaseId = getMappedRelease(requirement.get("target-rel"))
            def status = getMappedReqStatus(requirement.get("status"))
            def severity = getMappedReqSeverity(requirement.get("req-priority"))
            def typeId = getMappedType(reqTypeIds.get(requirement.get("type-id")))
            def lastChanged = requirement.get("last-modified")
            def creation = requirement.get("creation-time")
            def description = requirement.get("description") ?: ""
            def comments = requirement.get("comments") ?: ""

            String tooltip = createTooltip(description, comments, creation, lastChanged)

            appId = getMappedApplication(project)

            // create change and add it to corresponding map
            createOrUpdateChange(id, name, releaseId, status, severity, typeId, lastChanged, tooltip)
        }

        // defects will be skipped if not specified in query
        defects = getDefects()

        for (def defect : defects) {
            def id = "Defect " + defect.id
            def name = defect.get("name")
            def releaseId = getMappedRelease(defect.get("target-rel"))
            def status = getMappedDefectStatus(defect.get("status"))
            def severity = getMappedDefectSeverity(defect.get("severity"))
            def typeId = getMappedType("Defect") // defect is a hard coded type
            def lastChanged = defect.get("last-modified")
            def creation = defect.get("creation-time")
            def description = defect.get("description") ?: ""
            def comments = defect.get("dev-comments") ?: ""

            def tooltip = createTooltip(description, comments, creation, lastChanged)

            // create change and add it to corresponding map
            createOrUpdateChange(id, name, releaseId, status, severity, typeId, lastChanged, tooltip)
        }

        // post changes and update last executed property on integration provider
        postChangesToUcr()
        integrationProvider.property("lastExecution", new SimpleDateFormat("MM/dd/yyy").format(new Date())).save()
    }

    // create tooltip to be displayed on an Urbancode Release server change
    private String createTooltip(String description, String comments, String creation, String lastChanged) {
        String creationHeader = "<h3>Created On</h3>"
        String creationBody = "<p>${creation}</p>"
        String modifiedHeader = "<h3>Last Modified</h3>"
        String modifiedBody = "<p>${lastChanged}</p>"
        String descriptionHeader = "<h3>Description</h3>"
        String descriptionBody = "<p>${description}</p>"
        String commentHeader = "<h3>Comments</h3>"
        String commentBody = "<p>${comments}</p>"

        String htmlTooltip = "<div style=width: 960px; color: navy; background-color: pink; " +
            "border: 2px solid blue; padding: 5px;>" + creationHeader + creationBody +
            modifiedHeader + modifiedBody + descriptionHeader + descriptionBody +
            commentHeader + commentBody + "</div>"

        return htmlTooltip
    }

    private def postChangesToUcr() {
        if (updateChangeList) {
            def changesToUpdate = updateChangeList as Change[]
            new Change().put(changesToUpdate)
        }

        if (createChangeList) {
            def changesToCreate = createChangeList as Change[]
            new Change().post(changesToCreate)
        }

        println("${updateChangeList.size()} changes have been updated on the Urbancode Release server.")
        println("${createChangeList.size()} new changes have been imported to the Urbancode Release server.")
    }

    // set change properties and add change to update list or creation list
    private void createOrUpdateChange(def id, def name, def releaseId, def status,
        def severity, def typeId, def lastChanged, def tooltip) {

        Change change  // create change on the ucr server
        def integrationProvider = ucrClient.getIntegrationProvider()
        def existingChangesMap = ucrClient.getExistingChangesMap()
        def lastExecutionDate = ucrClient.getLastExecutionDate()

        // last date that this change was modified
        Date lastChangedDate = lastChanged ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(lastChanged) : null

        // create change type on the ucr server using ucr changetype id
        ChangeType changeType = new ChangeType().id(typeId)

        // no change has to be created or updated if the integration was already executed after the requirement was altered
        if (lastExecutionDate != null && lastChangedDate < lastExecutionDate) {
            return
        }

        // use change on ucr server if it is already imported, otherwise create new
        def existingChange = existingChangesMap.get(id)
        change = existingChange ?: new Change().integrationProvider(integrationProvider).setExternalId(id)
        change.name(name).application(appId).release(releaseId).status(status).severity(severity).type(changeType)

        // tooltip
        change.description(tooltip)

        if (existingChange) {
            updateChangeList.add(change)
        }
        else {
            createChangeList.add(change)
        }
    }

    private def getMappedApplication(String almProject) {
        def appEntity = new Application()

        // return the application matching the project name
        Application[] ucrApps = appEntity.filter("name", FilterClass.STRING, FilterType.EQUALS, almProject).when().getAll()

        if (ucrApps.length > 0) {
            return ucrApps[0]
        }
        else {
            return null
        }
    }

    // return the ucr release mapped to the specified alm release
    private def getMappedRelease(String almRelease) {
        def result = null
        def releaseMappings
        Release ucrRelease

        // attempt to automap the release by name, otherwise try to manually map
        if (autoMapReleases) {
            def releaseEntity = new Release()
            def allUcrReleases = releaseEntity.getAll()

            for (def release : allUcrReleases) {
                if (release.name == almRelease) {
                    ucrRelease = release
                    return ucrRelease
                }
            }
        }

        releaseMappings = parsePropertyMapping(mappingReleases)

        // compare each mapping and return the ucr release mapped to the specified alm release
        for (def releaseMapping : releaseMappings) {
            def almReleases = releaseMapping.get("almReleases")

            if (releaseMapping.get("almReleases").equals(almRelease)) {
                ucrRelease = new Release().id(releaseMapping.get("ucrReleases"))
                result = ucrRelease
                break
            }
        }

        return result
    }

    // return the ucr status mapped to the specified alm requirement status
    private def getMappedReqStatus(String reqStatus) {
        def result = null
        def statusMappings = parsePropertyMapping(mappingReqStatuses)

        // compare each mapping and return the ucr status mapped to the specified alm requirement status
        for (def statusMapping : statusMappings) {
            if (statusMapping.almReqStatuses.equals(reqStatus)) {
                result = Status.valueOf(statusMapping.ucrReqStatuses.replaceAll(' ', '_').toUpperCase())
            }
        }

        return result
    }

    // return the ucr severity mapped to the specified alm requirement severity
    private def getMappedReqSeverity(String reqSeverity) {
        def result = null
        def severitiesMappings = parsePropertyMapping(mappingReqSeverities)

        // compare each mapping and return the ucr severity mapped to the specified alm severity
        for (def severitiesMapping : severitiesMappings) {
            if (severitiesMapping.almReqSeverities.equals(reqSeverity)) {
                result = Severity.valueOf(severitiesMapping.ucrReqSeverities.replaceAll(' ', '_').toUpperCase())
            }
        }

        return result
    }

    // return the ucr status mapped to the specified alm defect status
    private def getMappedDefectStatus(String defectStatus) {
        def result = null
        def statusMappings = parsePropertyMapping(mappingDefectStatuses)

        // compare each mapping and return the ucr type mapped to the specified alm type
        for (def statusMapping : statusMappings) {
            if (statusMapping.almDefectStatuses.equals(defectStatus)) {
                result = Status.valueOf(statusMapping.ucrDefectStatuses.replaceAll(' ','_').toUpperCase())
            }
        }

        return result
    }

    // return the ucr severity mapped to the specified alm defect severity
    private def getMappedDefectSeverity(String defectSeverity) {
        def result = null
        def severitiesMappings = parsePropertyMapping(mappingDefectSeverities)

        // compare each mapping and return the ucr severity mapped to the specified alm severity
        for (def severitiesMapping : severitiesMappings) {
            if (severitiesMapping.almDefectSeverities.equals(defectSeverity)) {
                result = Severity.valueOf(severitiesMapping.ucrDefectSeverities.replaceAll(' ', '_').toUpperCase())
            }
        }

        return result
    }

    // return the ucr type mapped to the specified alm requirement type
    private def getMappedType(String almType) {
        def result = null
        def typeMappings = parsePropertyMapping(mappingTypes)

        // compare each mapping and return the ucr type mapped to the specified alm type
        for (def typeMapping : typeMappings) {
            if (typeMapping.almTypes.equals(almType)) {
                result = typeMapping.ucrTypes
            }
        }

        return result
    }
}