package com.urbancode.air.plugin.accuwork;

import com.urbancode.air.*

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern
import org.apache.commons.lang.math.RandomUtils;
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 AccuworkBase {

    //**************************************************************************
    // CLASS
    //**************************************************************************
    static public String systemTemp = System.getProperty("java.io.tmpdir")
    
    //**************************************************************************
    // INSTANCE
    //**************************************************************************
    String scmCommand = "accurev"

    String host = null
    String username = null
    String password = null
    String issueDB = null
    String depotName = null
    String issueUrl = null
    List<FileChange> changes = new ArrayList<FileChange>()

    File directory;
    
    final CommandHelper ch = new CommandHelper(directory);
    
    private boolean loggedIn = false
    private File tempDir;

    /**
     * Method to check if login is required and authenticate with provided credentials if needed/able
     */
    protected void login() {
        if (!username) {
            println "No user credentials provided. Skipping login detection and login."
            return
        }
        
        tempDir = new File(systemTemp, "accurev-" + RandomUtils.nextInt()).getAbsoluteFile()
        tempDir.mkdir()
        
        println "Setting ACCUREV_HOME to " + tempDir.getAbsolutePath()
        ch.addEnvironmentVariable("ACCUREV_HOME", tempDir.getAbsolutePath())
        
        boolean needsLogin = checkAuth()
        
        if (needsLogin) {
            // run login
            loggedIn = true
            
            if (username) {
                def loginCmd = [scmCommand, 'login']
                if (host) {
                    loginCmd << '-H' << host
                }
                
                loginCmd << username << password ?: ''
                
                ch.runCommand("Logging In with AccuRev", loginCmd);
            }
            else {
                println "No username found, continuing with anonymous access";
            }
        }
    }
    
    protected boolean checkAuth() {
        def infoCommand = [scmCommand, 'info']
        if (host) {
            infoCommand << '-H' << host
        }
        def infoText = new ByteArrayOutputStream();

        ch.runCommand("Check if authenticated", infoCommand) { Process proc ->
            proc.out.close() // close stdin
            def out = new PrintStream(System.out, true);
            try {
                proc.waitForProcessOutput(infoText, out) // forward stdout and stderr to autoflushing output stream
            }
            finally {
                out.flush();
            }
            println infoText;
        }
        
        boolean needsLogin = false
        
        // Check if a login is required
        if (infoText =~ /(.*)(P|p)rincipal:\s*\(not logged in\)/) {
            needsLogin = true
        }
        
        return needsLogin
    }
    
    protected void logout() {
        try {
            if (loggedIn) {
                def logoutCmd = [scmCommand, "logout"]
                
                if (host) {
                    logoutCmd << "-H" << host
                }

                ch.runCommand("Logging Out Of AccuRev", logoutCmd);
                
                loggedIn = false
            }
        }            
        finally {
            if (tempDir) {
                println "\nDeleting ACCUREV_HOME directory " + tempDir.getAbsolutePath() + "\n"
                IO.delete(tempDir);
            }
        }
    }
    
    protected String getChangeSetXml() {
        def authToken = System.getenv("AUTH_TOKEN")
        String buildLifeId = System.getenv("BUILD_LIFE_ID")
        
        // construct the URL with property replacements
        String baseUrl = System.getenv("WEB_URL")
        
        baseUrl += baseUrl.endsWith("/") ? "" : "/"
        String url = baseUrl + "rest/buildlife/${buildLifeId}/sourcechanges"
        
        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 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
    }
    
    protected void parseChangeSets(String changesXml) {
        new XmlSlurper().parseText(changesXml)."change-set".each { changeSetElem ->
            String changeSetId = changeSetElem."change-id"
            String repoId = changeSetElem."repository-id"

            String depotName = issueDB
            changeSetElem.properties.property.each { prop ->
                if (prop.name == "depotName") {
                    depotName = prop.value ? prop.value : depotName
                }
            }
            
            changeSetElem.properties.property.each { prop ->
                if (prop.name == "workItems") {
                    List<String> bugIds = prop.value.text().split(",")
                    
                    String depotPattern = '${depot}'
                    String issuePattern = '${issueId}'
                    for (String id : bugIds) {
                        String url = issueUrl?.replace(depotPattern, depotName)?.replace(issuePattern, id)
                        println "Adding issue ${id} from change set ${changeSetId} (repository ${repoId}) with URL ${url}"
                        changes.add(new FileChange(changeSetId, id, repoId, depotName, url))
                    }
                }
            }
        }
    }
    
    protected boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }
}
