/*
* 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.ContentType
import org.apache.http.entity.StringEntity
import org.codehaus.jettison.json.JSONObject

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

public class AddComment extends AutomationBase {
    
    //**************************************************************************
    // CLASS
    //**************************************************************************
    
    //**************************************************************************
    // INSTANCE
    //**************************************************************************
    
    String defectPattern
    String taskPattern
    String usPattern
    String comment
    
    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
    
    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 "Workspace Ref = ${workspaceRef}"
        
        try {
            String changesXml = getChangesets()
            
            if (changesXml) {
                parseChangesets(changesXml)
                
                addComments()
            }
            else {
                println "No source changes found."
            }
        }
        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
            }
            changeSetList.add(changeSetElem."comment".text())
        }
    }
    
    private void addComments() {
        if (usIdSet.size() == 0 && taskIdSet.size() == 0 && defectIdSet.size() == 0) {
            println "No Rally artifact references found in source changes."
        }
        else {
        	addComments(defectIdSet, defectIdToChangeSetListMap, 'defect', 'defect', comment)
        	addComments(taskIdSet, taskIdToChangeSetListMap, 'task', 'task', comment)
        	addComments(usIdSet, usIdToChangeSetListMap, 'user story', 'hierarchicalrequirement', comment)
        }
    }
    
    private void addComments(idSet, idToChangeSetListMap, type, rallyType, comment) {
        for (def id in idSet) {
        	def itemContent = findItemByName(workspaceRef, rallyType, id)
            def jsonResults = new JSONObject(itemContent)['QueryResult']['Results']
            if (jsonResults.length() > 0) {
            	def jsonResult = jsonResults.get(0)
            	def objectId = jsonResult['ObjectID']
                def changeLogText = idToChangeSetListMap.get(id).get(0)
                def jsonPost = new JSONObject()
                jsonPost.put('Artifact', url + "$rallyType/$objectId")
                jsonPost.put('Text', changeLogText + (comment ? "<div>$comment</div>" : ''))
                def json = new JSONObject()
                json.put('ConversationPost', jsonPost)
                println "Adding comment to $id"
                sendPost(url, json.toString())
           }
           else {
              println "Unable to find Rally $type $id"
           }
        }
    }
    
    private void sendPost(String postUrl, String json) {
    	def key = getKey()
        HttpPost postMethod = new HttpPost(postUrl + 'conversationpost/create?key=' + URLEncoder.encode(key, 'UTF-8'))
        try {
            postMethod.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON));

            def response = client.execute(postMethod)
            def responseCode = response.statusLine.statusCode
            def content = IO.readText(response.entity.content)
            def createResult = new JSONObject(content).get('CreateResult')
            def errors = createResult.get('Errors')
            def warnings = createResult.get('Warnings')

            if (errors.length() == 0 && isGoodResponseCode(responseCode)) {
          		if (warnings.length() > 0) {
              		println "Warnings: $warnings"
          		}
              	println "Successfully created comment"
            }
            else {
              	println "ERROR!!! Failed to create comment"
              	if (errors.length() > 0) {
              		println "Errors: $errors"
          		}
          		if (warnings.length() > 0) {
              		println "Warnings: $warnings"
          		}
                throw new RuntimeException("Rally server did not create comment. StatusCode: ${responseCode}")
            }
        }
        finally {
            postMethod.releaseConnection()
        }
    }
}