package com.urbancode.air.plugin.scm

import java.util.List;

import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.UsernamePasswordCredentials
import org.apache.commons.httpclient.auth.AuthScope
import org.apache.commons.httpclient.methods.StringRequestEntity
import org.apache.commons.httpclient.methods.GetMethod
import org.apache.commons.httpclient.methods.PostMethod
import org.apache.commons.httpclient.params.HttpClientParams
import org.apache.commons.httpclient.protocol.Protocol
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory

import com.urbancode.air.plugin.scm.changelog.*;
import com.urbancode.commons.util.IO
import com.urbancode.commons.util.https.OpenSSLProtocolSocketFactory

public class SCMChangelog extends SCMStep {
    
    def rtcSnapshot
    def rtcBaseline
    def startSnapshot
    def isStartBaseline
    def endSnapshot
    def isEndBaseline
    String startBuildLifeId
    String changesUrl
    
    final static def CHANGELOG_DATE_FORMAT = 'yyyy-MM-dd HH:mm:ss.S Z'
    final def RTC_CHANGELOG_OUT_DATE = new java.text.SimpleDateFormat("${CHANGELOG_DATE_FORMAT}")
    
    final ChangeSetFilter changeSetFilter = new ChangeSetFilter()
    List<ChangeSet> changeSets
    
    public def execute() {
        setUpSnapshots()
        
        if (startSnapshot && endSnapshot) {
            def componentSet
            if (!components && (isStartBaseline || isEndBaseline)) {
                componentSet = getWorkspaceInfo(null).componentList.keySet()
            }
            else if (components) {
                componentSet = components.split(',')
            }
            String changelogText = getChangelog(startSnapshot, isStartBaseline ? 'baseline' : 'snapshot',
                    endSnapshot, isEndBaseline ? 'baseline' : 'snapshot', componentSet)
            parseChangelog(changelogText)
            
            // Construct xml message and send to server
            if (changeSets) {
                ChangeSetXMLHelper xmlHelper = new ChangeSetXMLHelper()
                xmlHelper.repoType = REPO_TYPE
                String xml = xmlHelper.toXML(changeSets)
                sendPostRequest(xml)
            }
            else {
                println "No changes detected"
            }
        
        }
        else {
            println "Start Buildlife ID: ${startBuildLifeId}"
            println "Start Snapshot: ${startSnapshot} ${isStartBaseline?'is basline':''}"
            println "End Snapshot: ${endSnapshot} ${isEndBaseline?'is basline':''}"
            println "Cannot get a changelog without start and end snapshots!"
        }
    }
    
    private void setUpSnapshots() {
        def propertiesXml
        if (startBuildLifeId) {
            propertiesXml = getPropertiesXml(startBuildLifeId)
            parseProperties(propertiesXml)
            startSnapshot = rtcSnapshot
            isStartBaseline = rtcBaseline
        }
        
        String endBuildLifeId = System.getenv("BUILD_LIFE_ID")
        propertiesXml = getPropertiesXml(endBuildLifeId)
        parseProperties(propertiesXml)
        endSnapshot = rtcSnapshot
        isEndBaseline = rtcBaseline
    }
    
    private String getPropertiesXml(String buildLifeId) {
        def authToken = System.getenv("AUTH_TOKEN")
        
        // construct the URL with property replacements
        String baseUrl = System.getenv("WEB_URL")
        
        baseUrl += baseUrl.endsWith("/") ? "" : "/"
        String url = baseUrl + "rest/buildlife/${buildLifeId}/properties"
        
        println "Sending request to $url"
        
        ProtocolSocketFactory socketFactory = new OpenSSLProtocolSocketFactory()
        Protocol https = new Protocol("https", socketFactory, 443)
        Protocol.registerProtocol("https", https)

        GetMethod getMethod = new GetMethod(url)
        if (authToken) {
            getMethod.setRequestHeader("Authorization-Token", authToken)
        }

        HttpClient client = new HttpClient()

        def responseCode = client.executeMethod(getMethod)
        InputStream responseStream = getMethod.getResponseBodyAsStream()
        
        String propertiesXml = IO.readText(responseStream)
        
        if (!isGoodResponseCode(responseCode)) {
            throw new Exception("Failed to get build life properties from the server: $propertiesXml")
        }
        
        getMethod.releaseConnection()
        
        return propertiesXml
    }
    
    private void parseProperties(String propertiesXml) {
        new XmlSlurper().parseText(propertiesXml)."property".each { property ->
            String propName = property.@name
            if (propName == "rtc.snapshot") {
                rtcSnapshot = property.text() ?: ""
            }
            else if (propName == "rtc.isBaseline") {
                rtcBaseline = Boolean.valueOf(property.text())
            }
        }
    }
    
    /**
     * (1032) User:anthill Comment: 7, 15: Test work item - Change readme file.And try a new line Date:2010-03-16 10:13:53 -0500
     *      Work Item 7: Test - work - item
     *      Work Item 15: Test task
     *      /conf/jalopy.xml
     *      /readme.txt
     *
     */
    public def getChangelog(start, startType, end, endType, componentList) {
        def changelogText
        if (start && end) {
            if (componentList && componentList.size() > 0) {
                componentList.each { component ->
                    getCmdHelper().runCommand('Get Changelog',
                            [command, '--non-interactive', 'compare',
                                    startType, start,
                                    endType, end,
                                    '-I', 'wsf',
                                    '-f', 'i',
                                    '-C', 'User:{userid} Comment:',
                                    '-p', 'd',
                                    '-S', 'cdi',
                                    '-D', "'Date:'" + CHANGELOG_DATE_FORMAT,
                                    '-c', component,
                                    '-u', username,
                                    '-P', password,
                                    '-r', serverUrl]) { proc ->
                        proc.consumeProcessErrorStream(System.out)
                        proc.outputStream.close()
                        changelogText = proc.text.trim()
                        println changelogText
                    }
                }
            }
            else {
                getCmdHelper().runCommand('Get Changelog',
                        [command, '--non-interactive', 'compare',
                                startType, start,
                                endType, end,
                                '-I', 'wsf',
                                '-f', 'i',
                                '-C', 'User:{userid} Comment:',
                                '-p', 'd',
                                '-S', 'cdi',
                                '-D', "'Date:'" + CHANGELOG_DATE_FORMAT,
                                '-u', username,
                                '-P', password,
                                '-r', serverUrl]) { proc ->
                    proc.consumeProcessErrorStream(System.out)
                    proc.outputStream.close()
                    changelogText = proc.text.trim()
                    println changelogText
                }
            }
        }
        
        return changelogText
    }

    private void parseChangelog(changelogText) {
        def currentChangeSet
        def currentWorkItemList = []
        
        if (!changeSets) {
            changeSets = []
        }
        
        changelogText.eachLine { it ->
            def line = it.trim()
            if (line ==~ '\\(\\d+\\) User:.*') {
                line.find('\\((\\d+)\\) User:(.*) Comment:(.*) Date:(.*)') {match, id, user, comment, date ->
                    if (currentChangeSet) {
                        if (currentWorkItemList) {
                            currentChangeSet.properties['workItems'] = workItems.join(',');
                            currentChangeSet.message = currentChangeSet.message.substring(currentChangeSet.message.indexOf(':'))
                            currentChangeSet.message = currentChangeSet.message.substring(currentWorkItemList[0].name.size() + 3)
                        }
                        
                        if (addChangeSet(currentChangeSet)) {
                            changeSets << currentChangeSet
                        }
                        
                        currentWorkItemList.clear()
                    }
                    
                    currentChangeSet = new ChangeSet()
                    currentChangeSet.id = id
                    currentChangeSet.user = user
                    currentChangeSet.message = comment
                    currentChangeSet.date = RTC_CHANGELOG_OUT_DATE.parse(date)
                }
            }
            else if (line.startsWith('Work Item')) {
                line.find('Work Item (\\d+): (.+)') {match, workItemId ->
                    currentWorkItemList << workItemId
                }
            }
            // 4.0
            else if (line ==~ ('\\(\\d+:_[0-9A-Za-z\\-_]+\\) User:.*')) {
                line.find('\\(\\d+:(_[0-9A-Za-z\\-_]+)\\) User:(.*) Comment:(.*) Date:(.*)') {match, id, user, comment, date ->
                    if (currentWorkItemList) {
                        currentChangeSet.properties['workItems'] = workItems.join(',');
                        currentChangeSet.message = currentChangeSet.message.substring(currentChangeSet.message.indexOf(':'))
                        currentChangeSet.message = currentChangeSet.message.substring(currentWorkItemList[0].name.size() + 3)
                    }
                    
                    if (addChangeSet(currentChangeSet)) {
                        changeSets << currentChangeSet
                    }
                    
                    currentWorkItemList.clear()
                }
            }
            // 3.0
            else if (line ==~ ('\\(\\d+:_[0-9A-Za-z\\-_]+\\) Work Item.*')) {
                line.find('Work Item (\\d+): (.+)') {match, workItemId ->
                    currentWorkItemList << workItemId
                }
            }
            else if (line.size() > 0){
                ChangeSetFileEntry entry = new ChangeSetFileEntry()
                entry.path = line
                currentChangeSet.fileSet << entry
            }
        }
        if (currentChangeSet) {
            if (addChangeSet(currentChangeSet)) {
                changeSets << currentChangeSet
                
                if (currentWorkItemList) {
                    currentChangeSet.properties['workItems'] = workItems.join(',');
                    currentChangeSet.message = currentChangeSet.message.substring(currentChangeSet.message.indexOf(':') + 1)
                }
            }
        }
    }
    
    private boolean addChangeSet(ChangeSet changeSet) {
        boolean allowAuthor = changeSet.hasAllowedAuthor(changeSetFilter);
        boolean allowAnyPaths = changeSet.hasAllowedPath(changeSetFilter);
        if (!allowAuthor || !allowAnyPaths) {
            def message = new StringBuilder("Changeset ${changeSet.id} skipped because ")
            if (!allowAuthor) {
                message << "it has excluded author ${changeSet.user}"
            }
            if (!allowAuthor && !allowAnyPaths) {
                message << " and "
            }
            if (!allowAnyPaths) {
                message << "it contains only excluded file paths (${changeSet.fileSet.collect{it.path}})"
            }
            println message
        }
        
        return allowAuthor && allowAnyPaths
    }
    
    private void sendPostRequest(String xml) {
        // construct the URL with property replacements
        String url = changesUrl
        def authToken = System.getenv("AUTH_TOKEN")
        
        println "Sending request to $url"

        // Debug/testing stub
       if (url.startsWith("file://")) {
           File xmlOut = new File(directory, "xmlOut.xml")
           xmlOut << xml
       }
       else {
           ProtocolSocketFactory socketFactory = new OpenSSLProtocolSocketFactory()
           Protocol https = new Protocol("https", socketFactory, 443)
           Protocol.registerProtocol("https", https)
    
           PostMethod postMethod = new PostMethod(url)
           if (authToken) {
               postMethod.setRequestHeader("Authorization-Token", authToken)
               postMethod.setRequestHeader("Content-Type", "application/xml")
           }
           
           println "Sending ${changeSets.size()} changes"
           postMethod.setRequestEntity(new StringRequestEntity(xml));
    
           HttpClient client = new HttpClient()
    
           def responseCode = client.executeMethod(postMethod)
           InputStream responseStream = postMethod.getResponseBodyAsStream()
           if (isGoodResponseCode(responseCode)) {
               IO.copy(responseStream, System.out)
               println ""
           }
           else {
               IO.copy(responseStream, System.err)
               throw new RuntimeException("Failed to upload source changes. StatusCode: ${responseCode}")
           }
       }
    }
    
    private boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }
}