/*
* 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

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() {
        def authToken = System.getenv("AUTH_TOKEN")
        int buildLifeId = Integer.parseInt(System.getenv("BUILD_LIFE_ID"))
        
        String webUrl = System.getenv("WEB_URL")
        webUrl += webUrl.endsWith("/") ? "" : "/"
        String changesRequestUrl = webUrl + "rest/buildlife/${buildLifeId}/sourcechanges"
        
        println "\nGetting source changes from server"
        
        HttpGet getMethod = new HttpGet(changesRequestUrl)
        if (authToken) {
            getMethod.addHeader("Authorization-Token", authToken)
        }
        
        String changesXml
        
        try {
        	// create a new client so it doesn't have credentials for Rally on it
	        HttpClientBuilder builder = new HttpClientBuilder()
	        builder.setTrustAllCerts(true)
        	def client = builder.buildClient()

            HttpResponse response = client.execute(getMethod) 
            def responseCode = response.statusLine.statusCode
            InputStream responseStream = response.entity.content
            
            changesXml = IO.readText(responseStream)
            if (!isGoodResponseCode(responseCode)) {
                throw new Exception("Failed to get build life source changes from the server: $changesXml")
            }
        }
        finally {
            getMethod.releaseConnection()
        }
        
        return changesXml
    }
    
    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) {
        int buildLifeId = Integer.parseInt(System.getenv("BUILD_LIFE_ID"))
        def authToken = System.getenv("AUTH_TOKEN")
        String webUrl = System.getenv("WEB_URL")
        webUrl += webUrl.endsWith("/") ? "" : "/"
        
        String postUrl = webUrl + "rest/buildlife/${buildLifeId}/issues"
        
        println "Uploading issue document to the server"
        
        HttpPost postMethod = new HttpPost(postUrl)
        try {
            if (authToken) {
                postMethod.addHeader("Authorization-Token", authToken)
                postMethod.addHeader("Content-Type", "application/xml")
            }
            
            postMethod.setEntity(new StringEntity(issuesXml))
            
            HttpClientBuilder builder = new HttpClientBuilder()
            builder.setTrustAllCerts(true)
            HttpClient client = builder.buildClient()
            
            HttpResponse response = client.execute(postMethod)
            def responseCode = response.statusLine.statusCode
            InputStream responseStream = response.entity.content
            if (isGoodResponseCode(responseCode)) {
                IO.copy(responseStream, System.out)
                println ""
            }
            else {
                IO.copy(responseStream, System.err)
                throw new RuntimeException("Rally results upload to server failed. StatusCode: ${responseCode}")
            }
        }
        finally {
            postMethod.releaseConnection()
        }
    }
    
    /**
     * 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))
        }
    }
}