/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* (c) Copyright IBM Corporation 2012, 2014. 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.util.regex.Pattern

import org.apache.commons.httpclient.*
import org.apache.commons.httpclient.methods.*
import org.apache.commons.httpclient.protocol.*
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.codehaus.jettison.json.JSONObject
import org.jsoup.Jsoup

import com.urbancode.air.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO

import com.urbancode.ubuild.ucbtool.UCBTool

public class PublishDefectReport extends AutomationBase {

    //**************************************************************************
    // CLASS
    //**************************************************************************

    //**************************************************************************
    // INSTANCE
    //**************************************************************************

    String defectPattern
    String taskPattern
    String usPattern

    def workspaceRef
    def defectIdSet = [] as Set
    def defectIdToChangeSetListMap = [:]
    def taskIdSet = [] as Set
    def taskIdToChangeSetListMap = [:]
    def usIdSet = [] as Set
    def usIdToChangeSetListMap = [:]

    def defectArtifactPattern
    def taskArtifactPattern
    def usArtifactPattern

    String issueUrlBase

    public void execute() {
        setupHttpClient()

        if (defectPattern) {
            defectArtifactPattern = java.util.regex.Pattern.compile(defectPattern)
        }

        if (taskPattern) {
            taskArtifactPattern = java.util.regex.Pattern.compile(taskPattern)
        }

        if (usPattern) {
            usArtifactPattern = java.util.regex.Pattern.compile(usPattern)
        }

        println "Getting Workspace"
        workspaceRef = configureWorkspace()
        println "Found Workspace ${workspaceRef}"

        try {
            String changesXml = getChangesets()

            parseChangesets(changesXml)

            String issuesXml = createXml()

            if (issuesXml) {
                sendPost(issuesXml)
            }
        }
        catch (Exception e) {
            e.printStackTrace()
            System.exit(1)
        }
    }

    private String getChangesets() {
        UCBTool ucbTool = new UCBTool()
        def buildLifeId = System.getenv("BUILD_LIFE_ID")
        def baseUrl = System.getenv("WEB_URL")
        def authToken = System.getenv("AUTH_TOKEN")
        return ucbTool.getSourceChanges(baseUrl, authToken, buildLifeId)
    }

    private void parseChangesets(String changesXml) {
        if (!changesXml.contains("change-set")) {
            println "No changes detected. Nothing to publish."
            System.exit(0)
        }

        println "Parsing source changes for issues"

        new XmlSlurper().parseText(changesXml)."change-set".each { changeSetElem ->
            def defectMatcher
            def taskMatcher
            def usMatcher

            def changeComment = changeSetElem.'comment'.text()
            if (defectArtifactPattern) {
                defectMatcher = defectArtifactPattern.matcher(changeComment);
            }

            if (taskArtifactPattern) {
                taskMatcher = taskArtifactPattern.matcher(changeComment);
            }

            if (usArtifactPattern) {
                usMatcher = usArtifactPattern.matcher(changeComment);
            }

            findItems(defectMatcher, 'defect', defectIdSet, defectIdToChangeSetListMap, changeSetElem)
            findItems(taskMatcher, 'task', taskIdSet, taskIdToChangeSetListMap, changeSetElem)
            findItems(usMatcher, 'user story', usIdSet, usIdToChangeSetListMap, changeSetElem)
        }
    }

    private void findItems(matcher, type, idSet, idToChangeSetListMap, changeSetElem) {
        def changeComment = changeSetElem.'comment'.text()
        while (matcher?.find()) {
            def id
            if (matcher.groupCount() > 0 ) {
                // they specified a '(...)' group within the pattern, use that as the bug-id
                id = matcher.group(1)
            }
            else {
                // use the whole matching substring as the bug-id
                id = matcher.group()
            }

            println "Found $type identifier '$id' in change comment: $changeComment"
            idSet.add(id)
            def changeSetList = idToChangeSetListMap[id]
            if (!changeSetList) {
                changeSetList = []
                idToChangeSetListMap[id] = changeSetList
            }
            def changeId = changeSetElem."change-id"?.text() ? changeSetElem."change-id".text() : changeSetElem."id".text()
            changeSetList.add(changeId)
        }
    }

    private String createXml() {
        if (usIdSet.size() == 0 && taskIdSet.size() == 0 && defectIdSet.size() == 0) {
            println "No Rally artifact references found in source changes."
        }
        else {
            println "Creating issue document to upload to the server"
            def issuesXml = new java.io.StringWriter()
            def builder = new groovy.xml.MarkupBuilder(issuesXml)
            builder.issues() {
                createTypeXml(builder, defectIdSet, defectIdToChangeSetListMap, 'Defect', 'defect', 'Defect')
                createTypeXml(builder, taskIdSet, taskIdToChangeSetListMap, 'Task', 'task', 'Task')
                createTypeXml(builder, usIdSet, usIdToChangeSetListMap, 'User Story', 'hierarchicalrequirement', 'HierarchicalRequirement')
            }
            
            return issuesXml.toString()
        }
    }

    private void createTypeXml(builder, idSet, idToChangeSetListMap, type, rallyType, rallyDetailName) {
        for (def id in idSet) {
            println "Adding $type ${id}"
            def itemContent = findItemByName(workspaceRef, rallyType, id)
            def jsonResults = new JSONObject(itemContent)['QueryResult']['Results']
            if (jsonResults.length() > 0) {
                def jsonResult = jsonResults.get(0)
                println "Found Rally $type $id - ${jsonResult['_refObjectName']}"

                def objectId = jsonResult['ObjectID']
                def jsonDetails = new JSONObject(getItemDetails(rallyType, objectId))
                def jsonDetail = jsonDetails[rallyDetailName]

                // create the issue elements
                idToChangeSetListMap.get(id).each() { changeId ->
                    addIssueForUpload(builder, type, id, changeId, jsonDetail)
                }
            }
            else {
                println "Unable to find Rally $type $id"
            }
        }
    }

    private void sendPost(String issuesXml) {
        def descString = "Uploading issue document to the server"
        UCBTool ucbTool = new UCBTool()
        def buildLifeId = System.getenv("BUILD_LIFE_ID")
        def baseUrl = System.getenv("WEB_URL")
        def authToken = System.getenv("AUTH_TOKEN")
        ucbTool.addIssues(baseUrl, authToken, buildLifeId, issuesXml, descString)
    }

    /**
     * Generate a URL to a HTML page of the Defect, Task or User Story
     * @param jsonObject The JSON content from Rally of the item
     * @return the String value of the URL
     */
    private String getUrlForItem(jsonObject) {
        //https://rally1.rallydev.com/#/search?keywords=US1234
        issueUrlBase = issueUrlBase.endsWith("/") ? issueUrlBase : (issueUrlBase + "/")
        return issueUrlBase  + "#/search?keywords=" + jsonObject['FormattedID']
    }

    private void addIssueForUpload(builder, issueType, id, changeId, jsonObject) {
        builder.'issue'(id: id, "issue-tracker": "Rally", "change-id": changeId) {
            builder.'name'(jsonObject['_refObjectName'])
            builder.'type'(issueType)
            builder.'description'(Jsoup.parse(jsonObject['Description']).text())
            def jsonOwner = jsonObject['Owner']
            if (jsonOwner && !JSONObject.NULL.equals(jsonOwner)) {
                def ownerName = jsonOwner['_refObjectName']
                if (ownerName) {
                    builder.'owner'(ownerName)
                }
            }
            builder.'status'(jsonObject.opt('State') ? jsonObject.opt('State') : jsonObject.opt('ScheduleState'))
            builder.'url'(getUrlForItem(jsonObject))
        }
    }
}