package com.urbancode.air.plugin.scm

import java.util.Map;
import java.text.SimpleDateFormat

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.commons.util.IO
import com.urbancode.commons.util.https.OpenSSLProtocolSocketFactory

public class SCMCheckout extends SCMStep {
    
    def currentBuildlifeSnapshot
    
    public def execute() {
        def props = createSnapshot()
        updateWorkspace()
        return props
    }
    
    private void updateWorkspace() {
        // try to get the components of the workspace, if we fail we need to create it, if not we need to update it if there
        // there are changes to the components/baselines/snapshots
        def wsInfo = getWorkspaceInfo(null)
        if (wsInfo.cmdError.length() > 0) {
            if (wsInfo.cmdError.indexOf('Ambiguous') != -1) {
                // this is the case of multiple workspaces with the same name if name and not alias was used
                throw new Exception("More than one workspaces exist for name ${props['source/workspace']}, please remove duplicates or use an alias!")
            }
            else if (wsInfo.cmdError.indexOf('Could not find workspace') != -1) {
                // error was due to a missing workspace
                createWorkspace()
                wsInfo = getWorkspaceInfo(null)
                updateWorkspaceComponents(wsInfo.componentList)
                changeWorkspaceBaseline()
                cleanup(null)
                loadWorkspace()
            }
            else {
                // unknown error
                throw new Exception(wsInfo.cmdError.toString())
            }
        }
        else {
            changeWorkspaceFlowStream(wsInfo.workspaceStreamName, wsInfo.workspaceStreamAlias)
            if (!baseline) {
                changeWorkspaceSnapshot()
            }
            updateWorkspaceComponents(wsInfo.componentList)
            changeWorkspaceBaseline()
            try {
                repair()
                undo(null)
            }
            catch (Exception e) {
                println "Workspace Repair failed, doing a complete load!"
                cleanup(null)
                loadWorkspace()
        
            }
        }
    }
    
    private void updateWorkspaceComponents(currentComponentList) {
        if (currentComponentList) {
            currentComponentList.each { key, value ->
                currentComponentList."${key}" = currentComponentList."${key}".toLowerCase()
            }
            def removeComponentList = []
            def addComponentList = []

            if (components) {
                // user provided a list of components
                components.split(',').each {it ->
                    if (!currentComponentList.containsKey(it.toLowerCase()) && !currentComponentList.containsValue(it.toLowerCase())) {
                        addComponentList << it
                    }
                }
                def componentList = components.toLowerCase().split(',').toList()
                currentComponentList.each { key, value ->
                    if (!componentList.contains(key) && !componentList.contains(value)) {
                        removeComponentList << key
                    }
                }
            }
            else {
                // user did not provide component list, assuming all
                def targetList = getStreamOrSnapshotComponents()
                targetList.each { key, value ->
                    if (!currentComponentList.containsKey(key)) {
                        addComponentList << key
                    }
                }
                currentComponentList.each { key, value ->
                    if (!targetList.containsKey(key)) {
                        removeComponentList << key
                    }
                }
            }

            if (removeComponentList.size() > 0) {
                removeComponents(removeComponentList)
            }
            if (addComponentList.size() > 0) {
                addComponents(addComponentList)
            }
        }
    }

    private void addComponents(componentList) {
        def cmd = [command, '--non-interactive', 'workspace', 'add-components',
                '-r', serverUrl,
                '-u', username,
                '-P', password,
                workspace]
        componentList.each {it ->
            cmd << it
        }
        getCmdHelper().runCommand('Add Workspace components', cmd)
    }

    private void removeComponents(componentList) {
        def cmd = [command, '--non-interactive', 'workspace', 'remove-components',
                '-r', serverUrl,
                '-u', username,
                '-P', password,
                workspace]
        componentList.each {it ->
            cmd << it
        }
        getCmdHelper().runCommand('Remove Workspace components', cmd)
    }
    
    private void changeWorkspaceBaseline() {
        if (baseline) {
            getCmdHelper().runCommand('Change Workspace Baseline',
                    [command, '--non-interactive', 'workspace', 'rplc',
                    '-o',
                    '-b', baseline,
                    '--all',
                    '-r', serverUrl,
                    '-u', username,
                    '-P', password,
                    workspace,
                    'stream',
                    stream])
        }
    }
    
    public Map getWorkspaceInfo(dir) {
        if (dir == null) {
            dir = directory
        }
        def componentText
        def error = new StringBuffer()
        def result = [:]
        result.componentList = [:]
        try {
            getCmdHelper().runCommand('Get workspace components', [command, '--non-interactive', 'list', 'components', workspace, '-u', username, '-P', password, '-r', serverUrl]) {proc ->
                proc.consumeProcessErrorStream(error)
                proc.outputStream.close()
                componentText = proc.text.trim()
                println componentText
                println error
                result.cmdError = error
            }
        }
        catch (Exception e) {}
        if (error.length() == 0) {
            componentText.eachLine {it ->
                def line = it.trim()
                if (line.startsWith('Workspace')) {
                    line.find('.*<-> \\((\\d+)\\) "(.*)"') {match, streamAlias, streamName ->
                        result.workspaceStreamAlias = streamAlias
                        result.workspaceStreamName = streamName
                    }
                } else if (line.startsWith('Component') && line[-1] != ')') {
                    line.find('Component: \\((\\d+)\\) "(.+)"') {match, compAlias, compName ->
                        result.componentList."${compAlias}" = compName
                    }
                }
            }
        }
        return result
    }

    public Map getStreamOrSnapshotComponents() {
        def componentText
        def result = [:]
        def cmdArgs = [command, '--non-interactive', 'list', 'components'];
        if (snapshot) {
            cmdArgs << '-s';
        }
        cmdArgs << '-u';
        cmdArgs << username;
        cmdArgs << '-P';
        cmdArgs << password;
        cmdArgs << '-r';
        cmdArgs << serverUrl;

        if (snapshot) {
            cmdArgs << snapshot;
        }
        else {
            cmdArgs << stream;
        }
        getCmdHelper().runCommand('List stream components', cmdArgs) {proc ->
            proc.consumeProcessErrorStream(System.out)
            proc.outputStream.close()
            componentText = proc.text.trim()
            println componentText
        }
        componentText.eachLine {it ->
            def line = it.trim()
            if (line.startsWith('Component') && line[-1] != ')') {
                line.find('Component: \\((\\d+)\\) "(.+)"') {match, compAlias, compName ->
                    result."${compAlias}" = compName
                }
            }
        }
        return result
    }

    public def getStreamSnapshots() {
        def snapshotText
        def result = []
        def dateFormat = new SimpleDateFormat(listSnapshotDateFormat)
        getCmdHelper().runCommand('List stream snapshots', [command, '--non-interactive', 'list', 'snapshots',
                stream,
                '-u', username,
                '-P', password,
                '-r', serverUrl]) {proc ->
            proc.consumeProcessErrorStream(System.out)
            proc.outputStream.close()
            snapshotText = proc.text.trim()
            println snapshotText
        }
        snapshotText.eachLine {it ->
            def line = it.trim()
            line.find('\\((\\d+)\\) "(.+)" (.*)') {match, alias, name, date ->
                result << [alias:alias, date:dateFormat.parse(date)]
            }
        }
        return result
    }

    private def createSnapshot() {
        if (currentBuildlifeSnapshot) {
            println "Snapshot has already been created, nothing to do!"
        }
        else {
            if (snapshot || baseline) {
                println "Building from ${snapshot ? 'snapshot' : 'baseline'} so no need to create a new snapshot!"
            }
            else {
                getCmdHelper().runCommand('Create snapshot', [command, '--non-interactive', 'create', 'snapshot',
                        '-n', buildSnapshot,
                        '-d', buildSnapshotDescription,
                        stream,
                        '-r', serverUrl,
                        '-u', username,
                        '-P', password])
            }
            def newProps = ["${buildSnapshotProperty}":"${snapshot ?: (baseline?:buildSnapshot)}"]
            if (baseline) {
                newProps['rtc.isBaseline'] = 'true'
            }
            
            return newProps
        }
    }

    private void createWorkspace() {
        def cmd = [command, '--non-interactive', 'create', 'workspace',
                '-d', 'Created by uBuild',
                '-r', serverUrl,
                '-u', username,
                '-P', password]
        if (snapshot) {
            cmd << '--snapshot' << snapshot
        }
        else if (baseline) {
            cmd << '-s' << stream
        }
        else {
            cmd << '--snapshot' << buildSnapshot
        }
        cmd << workspace

        getCmdHelper().runCommand('Create workspace', cmd)
    }

    

    private void changeWorkspaceSnapshot() {
        getCmdHelper().runCommand('Change Workspace Snapshot',
                [command, '--non-interactive', 'workspace', 'rplc',
                '-o',
                '--all',
                '-r', serverUrl,
                '-u', username,
                '-P', password,
                workspace,
                'snapshot',
                snapshot ?: buildSnapshot])
    }

    private void changeWorkspaceFlowStream(currentStream, currentStreamAlias) {
        if (stream && !stream.equalsIgnoreCase(currentStream) && !stream.equalsIgnoreCase(currentStreamAlias)) {
            getCmdHelper().runCommand('Change Workspace Flow Stream',
                    [command, '--non-interactive', 'change-target', 'workspace',
                    '-r', serverUrl,
                    '-u', username,
                    '-P', password,
                    workspace,
                    stream])
        }
    }

    private void loadWorkspace() {
        def dir = directory.canonicalFile
        if (!dir.exists()) {
            dir.mkdirs()
        }
        def cmd = [command, '--non-interactive', 'load',
                '--all',
                '-d', dir.toString(),
                '-r', serverUrl,
                '-u', username,
                '-P', password]
        if (isForce) {
            cmd << '-f'
        }
        if (isIncludeRoot) {
            cmd << '-i'
        }
        cmd << workspace
        getCmdHelper().runCommand('Load workspace', cmd)
    }
    
    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: $changesXml")
        }
        
        getMethod.releaseConnection()
        
        return propertiesXml
    }
    
    private void parseProperties(String propertiesXml) {
        new XmlSlurper().parseText(changesXml)."property".each { property ->
            String propName = property.@name
            if (propName == buildSnapshotProperty) {
                currentBuildlifeSnapshot = property.text() ?: ""
            }
        }
    }
}