package com.urbancode.air.plugin.automation;

import com.urbancode.air.*
import org.apache.commons.httpclient.*
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.protocol.*

import com.urbancode.commons.util.IO
import com.urbancode.commons.util.https.OpenSSLProtocolSocketFactory

import java.util.regex.Pattern

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)
                
                createXml()
            }
            else {
                println "No changelog 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 "Getting source changes from server"
        ProtocolSocketFactory socketFactory = new OpenSSLProtocolSocketFactory()
        Protocol https = new Protocol("https", socketFactory, 443)
        Protocol.registerProtocol("https", https)
        
        GetMethod getMethod = new GetMethod(changesRequestUrl)
        if (authToken) {
            getMethod.setRequestHeader("Authorization-Token", authToken)
        }
        
        def responseCode = client.executeMethod(getMethod)
        InputStream responseStream = getMethod.getResponseBodyAsStream()
        
        String changesXml = IO.readText(responseStream)
        
        if (!isGoodResponseCode(responseCode)) {
            throw new Exception("Failed to get build life source changes from the server: $changesXml")
        }
        
        getMethod.releaseConnection()
        
        return changesXml
    }
    
    private void parseChangesets(String changesXml) {
        def defectMatcher
        def taskMatcher
        def usMatcher
        
        println "Parsing Changelog"

        new XmlSlurper().parseText(changesXml)."change-set".each { changeSetElem ->
            if (defectArtifactPattern) {
                defectMatcher = defectArtifactPattern.matcher(changeSetElem.comment.text());
            }
            
            if (taskArtifactPattern) {
                taskMatcher = taskArtifactPattern.matcher(changeSetElem.comment.text());
            }
            
            if (usArtifactPattern) {
                usMatcher = usArtifactPattern.matcher(changeSetElem.comment.text());
            }
            
            while (defectMatcher?.find()) {
                def defectId
                if (defectMatcher.groupCount() > 0 ) {
                    // they specified a '(...)' group within the pattern, use that as the bug-id
                    defectId = defectMatcher.group(1)
                }
                else {
                    // use the whole matching substring as the bug-id
                    defectId = defectMatcher.group()
                }
                
                println "Found defect identifier in change comment: $defectId"
                defectIdSet.add(defectId)
                def changeSetList = defectIdToChangeSetListMap[defectId]
                if (!changeSetList) {
                    changeSetList = []
                    defectIdToChangeSetListMap[defectId] = changeSetList
                }
                changeSetList.add(changeSetElem."comment".text())
            }
            while (taskMatcher?.find()) {
                def taskId
                if (taskMatcher.groupCount() > 0 ) {
                    // they specified a '(...)' group within the pattern, use that as the bug-id
                    taskId = taskMatcher.group(1)
                }
                else {
                    // use the whole matching substring as the bug-id
                    taskId = taskMatcher.group()
                }
                
                println "Found task identifier in change comment: $taskId"
                taskIdSet.add(taskId)
                def changeSetList = taskIdToChangeSetListMap[taskId]
                if (!changeSetList) {
                    changeSetList = []
                    taskIdToChangeSetListMap[taskId] = changeSetList
                }
                changeSetList.add(changeSetElem."comment".text())
            }
            while (usMatcher?.find()) {
                def usId
                if (usMatcher.groupCount() > 0 ) {
                    // they specified a '(...)' group within the pattern, use that as the bug-id
                    usId = usMatcher.group(1)
                }
                else {
                    // use the whole matching substring as the bug-id
                    usId = usMatcher.group()
                }
                
                println "Found User Story identifier in change comment: $usId"
                usIdSet.add(usId)
                def changeSetList = usIdToChangeSetListMap[usId]
                if (!changeSetList) {
                    changeSetList = []
                    usIdToChangeSetListMap[usId] = changeSetList
                }
                changeSetList.add(changeSetElem."comment".text())
            }
        }
    }
    
    private void createXml() {
        if (usIdSet.size() == 0 && taskIdSet.size() == 0 && defectIdSet.size() == 0) {
            println "No Rally artifact references found in build life changes."
        }
        else {
            for (def defectId in defectIdSet) {
                def defectData = null
                HttpMethod method = null
                def response = null
                def result = null
                
                try {
                   method = new GetMethod(url + "defect?workspace=" + URLEncoder.encode(workspaceRef, "UTF-8") + 
                       "&query=" + URLEncoder.encode("(FormattedID = " + defectId + ")" ,"UTF-8") + "&" + URLEncoder.encode("fetch", "UTF-8") + 
                           "=" + URLEncoder.encode("true", "UTF-8"))
                   result = client.executeMethod(method)
                   defectData = IO.readText(method.getResponseBodyAsStream())
                }
                finally {
                    if(method != null) {
                       method.releaseConnection();
                    }
                }
                def defectResult = new XmlParser().parseText(defectData).Results.Object;
                if (defectResult.size() > 0) {
                    // Generate XML doc
                    def writer = new StringWriter() 
                    def builder = new groovy.xml.MarkupBuilder(writer) 

                    def changeLogText = defectIdToChangeSetListMap.get(defectId).get(0)
                    println "Adding change comment for: $defectId"
                    builder.ConversationPost() {
                        Artifact(defectResult.ObjectID[0].text())
                        Text(changeLogText + (comment ? " " + comment : ''))
                        Workspace(workspaceRef)
                    }
                    
                    sendPost(url, writer.toString())
               }
               else {
                  println "Unable to find Rally defect $defectId"
               }
            }
            for (def taskId in taskIdSet) {
                def taskData = null
                HttpMethod method = null
                def response = null
                def result = null
                
                try {
                   method = new GetMethod(url + "task?workspace=" + URLEncoder.encode(workspaceRef, "UTF-8") + 
                       "&query=" + URLEncoder.encode("(FormattedID = " + taskId + ")" ,"UTF-8") + "&" + URLEncoder.encode("fetch", "UTF-8") + 
                           "=" + URLEncoder.encode("true", "UTF-8"))
                   result = client.executeMethod(method)
                   taskData = IO.readText(method.getResponseBodyAsStream())
                }
                finally {
                    if(method != null) {
                       method.releaseConnection();
                    }
                }
                def taskResult = new XmlParser().parseText(taskData).Results.Object;
                if (taskResult.size() > 0) {
                    // Generate XML doc
                    def writer = new StringWriter() 
                    def builder = new groovy.xml.MarkupBuilder(writer) 
                    def changeLogText = taskIdToChangeSetListMap.get(taskId).get(0)
                    println "Adding change comment for: $taskId"
                    builder.ConversationPost(){
                        Artifact(taskResult.ObjectID[0].text())
                        Text(changeLogText + (comment ? " " + comment : ''))
                        Workspace(workspaceRef)
                    }
                    
                    sendPost(url, writer.toString())
                }
                else {
                    println "Unable to find Rally task $taskId"
                }
            }
            for (def usId in usIdSet) {
                def usData = null
                HttpMethod method = null
                def response = null
                def result = null
                
                try {
                   method = new GetMethod(url + "hierarchicalrequirement?workspace=" + URLEncoder.encode(workspaceRef, "UTF-8") + 
                       "&query=" + URLEncoder.encode("(FormattedID = " + usId + ")" ,"UTF-8") + "&" + URLEncoder.encode("fetch", "UTF-8") + 
                           "=" + URLEncoder.encode("true", "UTF-8"))
                   result = client.executeMethod(method)
                   usData = IO.readText(method.getResponseBodyAsStream())
                }
                finally {
                    if(method != null) {
                       method.releaseConnection();
                    }
                }
                def usResult = new XmlParser().parseText(usData).Results.Object;
                if (usResult.size() > 0) {
                    // Generate XML doc
                    def writer = new StringWriter() 
                    def builder = new groovy.xml.MarkupBuilder(writer) 
                    def changeLogText = usIdToChangeSetListMap.get(usId).get(0)
                    out.println("Adding change comment for: $usId")
                    builder.ConversationPost(){
                        Artifact(usResult.ObjectID[0].text())
                        Text(changeLogText + (comment ? " " + comment : ''))
                        Workspace(workspaceRef)
                    }
                    
                    sendPost(url, writer.toString())
                }
                else {
                    println "Unable to find Rally User Story $usId"
                }
            }
        }
    }
    
    private void sendPost(String postUrl, String xml) {
        ProtocolSocketFactory socketFactory = new OpenSSLProtocolSocketFactory()
        Protocol https = new Protocol("https", socketFactory, 443)
        Protocol.registerProtocol("https", https)
 
        PostMethod postMethod = new PostMethod(postUrl + "conversationpost/create")
        try {
            postMethod.setRequestEntity(new StringRequestEntity(xml));

            def responseCode = client.executeMethod(postMethod)
            InputStream responseStream = postMethod.getResponseBodyAsStream()
            if (isGoodResponseCode(responseCode)) {
                IO.copy(responseStream, System.out)
                println "Successfully created comment"
                println ""
            }
            else {
                IO.copy(responseStream, System.err)
                throw new RuntimeException("Rally server did not create comment. StatusCode: ${responseCode}")
            }
        }
        finally {
            postMethod.releaseConnection()
        }
    }
    
    private boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }
}