/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * (c) Copyright IBM Corporation 2014, 2016. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */

import com.urbancode.air.AirPluginTool
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO
import com.urbancode.ud.client.ComponentClient
import com.urbancode.ud.client.VersionClient
import groovy.json.JsonSlurper
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.util.EntityUtils

import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.Charset
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream

def tempDirs = new HashMap<String, File>()

// Get step properties
def apTool  = new AirPluginTool(args[0], args[1])
def props = apTool.getStepProperties()

final def agentProps = new Properties()
agentProps.load(new FileInputStream(new File(System.getenv("AGENT_HOME"),
        "conf/agent/installed.properties")))
String charsetName = agentProps.getProperty("system.default.encoding")
Charset charset = null
if (charsetName != null) {
    charset = Charset.forName(charsetName)
}

final def REPO_PATH_SEPARATOR = "/"
def exitVal = 0

boolean isUseVFS = Boolean.valueOf(props['isUseVFS'])
def componentName = props['componentName']
def username = props['user']
def password = props['password']
def groupId = props['groupId']
def artifactId = props['artifactId']
def extension = props['extension']
def copyCount = props['copyCount']
def versionPattern = props['versionPattern']
def specificVersionPattern = props['version']
def saveFileExecuteBits = props['saveFileExecuteBits']
def separator = props['separator'].trim()
String statuses = props['statuses']
String links = props['links']
Boolean unzipArchive = Boolean.valueOf(props['unzipArchive'])

def extensionsString = props['extensions']
String[] extensions = new String[0]
if (! isEmpty(extensionsString)) {
    extensions = extensionsString.split(",")
    for (int i = 0; i < extensions.length; i++) {
        extensions[i] = extensions[i].trim()
    }
}

def repoName = props['repoName']
String repoUrl = props['repoUrl']
while (repoUrl.endsWith(REPO_PATH_SEPARATOR)) {
    repoUrl = repoUrl.substring(0, repoUrl.length() - 1)
}

String[] statusList = statuses?.split(',')*.trim()
String[] linkList = links?.split(',')*.trim()
String[] extensionList = extension?.split(',')*.trim()

//replace dots in groupID with slashes for rest calls
String encodedGroupId = groupId.replaceAll("\\.", "\\/")

boolean isNuget = Boolean.valueOf(!isEmpty(props['nuGetPackage']) && !isEmpty(props['nuGetVersion']))

String UDUsername = "PasswordIsAuthToken"
String UDPassword = String.format("{\"token\": \"%s\"}", System.getenv("AUTH_TOKEN"))

String webUrl = System.getenv("AH_WEB_URL")
URI url = new URI(webUrl)

VersionClient versionClient = new VersionClient(url, UDUsername, UDPassword)
ComponentClient componentClient = new ComponentClient(url, UDUsername, UDPassword)

HttpClientBuilder builder = new HttpClientBuilder()
if (username) {
    builder.setPreemptiveAuthentication(true)
    builder.setUsername(username)
    builder.setPassword(password)
}
DefaultHttpClient client = builder.buildClient()

def getTempDir = { version ->
    File result = tempDirs.get(version)
    if (result == null) {
        result = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString())
        IO.mkdirs(result)
        tempDirs.put(version, result)
    }

    return result
}

def isEmpty(value) {
    return value == null || value.equals("")
}

def filterVersions = { versionList ->
    def result = []
    def versionsMatched = Integer.valueOf(copyCount)
    for (version in versionList) {
        // If we still need versions (Cannot break in Groovy)
        if (versionsMatched > 0) {
            boolean shouldAdd = false
            // Make sure the overall component source configuration pattern is matched
            def versionPatternMatcher = version =~ versionPattern
            if (versionPatternMatcher.matches()) {

                // If they specified a specific version on the import dialog, only get that one
                if (specificVersionPattern != null && !specificVersionPattern.isEmpty()) {
                    def specificVersionMatcher = version =~ specificVersionPattern
                    if (specificVersionMatcher.matches()) {
                        shouldAdd = true
                    }
                } else {
                    // They want all the versions that match the source config pattern
                    shouldAdd = true
                }
            }

            if (shouldAdd) {
                result.add(version)
                versionsMatched--
            }
        }
    }

    return result
}

def getVersionList = {
    String versionUrl = (repoUrl + REPO_PATH_SEPARATOR + "api/storage/" + repoName + REPO_PATH_SEPARATOR
            + encodedGroupId + REPO_PATH_SEPARATOR + artifactId).toString()
    URI versionUri = new URI(versionUrl)
    println ("[Action] Getting list of versions for: " + versionUri.toString())
    HttpGet get = new HttpGet(versionUri.toString())
    HttpResponse response = client.execute(get)
    int status = response.getStatusLine().getStatusCode()
    if (status == HttpStatus.SC_OK) {
        def versions = []
        def jsonString = EntityUtils.toString(response.getEntity())
        def slurper = new JsonSlurper()
        def children = slurper.parseText(jsonString).children
        for (child in children) {
            def isFolder = child.folder.toString()
            if (isFolder.equals("true")) {
                def currVersion = child.uri.toString()
                versions.add(0, currVersion.substring(1, currVersion.length()))
            }
        }
        return versions
    }
    else {
        throw new Exception("Exception searching: " + versionUri.toString() + "\nErrorCode : " + status.toString())
    }
}

def getLatestVersions = {
    def versionList = getVersionList()

    if (isEmpty(versionPattern)) {
        versionPattern = ".*"
    }
    return filterVersions(versionList)
}

def setVersionProperties = { version ->
    println ("[Action] Setting version properties for version: " + version)
    //Set unzipped boolean value
    versionClient.setVersionProperty(version.toString(), componentName, "isUnzipped", unzipArchive.toString(), false)

    //Get Artifactory version properties and add to UCD version properties
    String propertiesUrl = (repoUrl + REPO_PATH_SEPARATOR + "api/storage/" + repoName + REPO_PATH_SEPARATOR
        + encodedGroupId + REPO_PATH_SEPARATOR + artifactId + REPO_PATH_SEPARATOR + version + "?properties").toString()
    URI propertiesUri = new URI(propertiesUrl)
    HttpGet get = new HttpGet(propertiesUri)
    HttpResponse response = client.execute(get)
    int status = response.getStatusLine().getStatusCode()
    if (status == HttpStatus.SC_OK) {
        def properties = []
        def jsonString = EntityUtils.toString(response.getEntity())
        def slurper = new JsonSlurper()
        def children = slurper.parseText(jsonString)?.properties
        for (prop in children) {
            // Set all Artifactory properties as component version properties
            versionClient.setVersionProperty(version.toString(), componentName, prop.getKey().toString(), prop.getValue()[0].toString(), false)
            // If statuses are specified and exist as Artifactory properties, add them to version
            if (statuses) {
                for (versStatus in statusList) {
                    if (versStatus.equals(prop.getKey().toString())) {
                        try {
                            println ("[Action] Property for status: '${versStatus}' found, adding status: '" + prop.getValue()[0].toString() + "'")
                            componentClient.addComponentVersionStatus(componentName, version.toString(), prop.getValue()[0].toString())
                        }
                        catch (IOException e) {
                            println ("[Warning] Property not set because UCD status '" + prop.getValue()[0].toString() + "' does not exist.")
                            println ("[Possible Solution] Create version status for '" + prop.getValue()[0].toString() + "' on UCD server.")
                        }
                    }
                }
            }
            // If links are specified and exist as Artifactory properties, add them to version
            if (links) {
                for (link in linkList) {
                    if (link.equals(prop.getKey().toString())) {
                        try {
                            println ("[Action] Property for link: '${link}' found, adding link: '" + prop.getValue()[0].toString() + "'")
                            componentClient.addComponentVersionLink(componentName, version.toString(), prop.getKey().toString(), prop.getValue()[0].toString())
                        }
                        catch (IOException e) {
                            println ("[Warning] Property '" + prop.getKey().toString() + "' not set as UCD link")
                        }
                    }
                }
            }
        }
    }
    else if (status == HttpStatus.SC_NOT_FOUND || status == HttpStatus.SC_NO_CONTENT) {
        println ("[Info] No Artifactory properties found for artifact.")
    }
    else {
        println ("[ERROR] A problem was encountered while setting Artifactory properties as version properties")
        throw new Exception("Exception searching: " + propertiesUri.toString() + "\nErrorCode : " + status.toString())
    }
}

def downloadFile = { fileUrl, version ->
    fileUrl = fileUrl.replace("api/storage/", "")
    println "[Action] Downloading file: " + fileUrl
    HttpGet get = new HttpGet(fileUrl)
    HttpResponse response = client.execute(get)
    int status = response.getStatusLine().getStatusCode()
    if (status == HttpStatus.SC_OK) {
        String[] currFile = fileUrl.split(REPO_PATH_SEPARATOR)
        File file = new File(getTempDir(version), currFile[currFile.length - 1])
        IO.copy(response.getEntity().getContent(), file)
        return getTempDir(version)
    }
    else {
        throw new Exception("Exception downloading file : " + fileUrl + "\nErrorCode : " + status.toString())
    }
}

def download = { version ->
    String downloadUrl = (repoUrl + REPO_PATH_SEPARATOR + "api/storage/" + repoName + REPO_PATH_SEPARATOR
            + encodedGroupId + REPO_PATH_SEPARATOR + artifactId + REPO_PATH_SEPARATOR + version).toString()
    URI downloadUri = new URI(downloadUrl)
    println "[Action] Downloading version: " + downloadUri.toString()

    HttpGet get = new HttpGet(downloadUri)
    HttpResponse response = client.execute(get)
    int status = response.getStatusLine().getStatusCode()
    if (status == HttpStatus.SC_OK) {
        def files = []
        def jsonString = EntityUtils.toString(response.getEntity())
        def slurper = new JsonSlurper()
        def children = slurper.parseText(jsonString).children
        for (child in children) {
            def isFolder = child.folder.toString()
            if (isFolder.equals("false")) {
                def currChild = child.uri.toString()
                for (fileType in extensionList) {
                    if (currChild =~ fileType + "\$") {
                        downloadFile(downloadUrl + currChild, version)
                    }
                }
            }
        }

        return getTempDir(version)
    }
}

private boolean unzipFile(File zipFile, File destDir) throws IOException {
    boolean unzipped = false

    final int BUFFER_SIZE = 4096
    ZipInputStream zipInput = new ZipInputStream(new FileInputStream(zipFile))
    ZipEntry zipEntry = zipInput.getNextEntry()

    if (!destDir.exists()) {
        destDir.mkdir()
    }

    while (zipEntry != null) {
        unzipped = true
        File entry = new File(destDir, zipEntry.getName())
        entry.deleteOnExit()

        if (zipEntry.isDirectory()) {
            entry.mkdir()
        }
        else {
            try {
                FileOutputStream fileOutput = new FileOutputStream(entry)
                BufferedOutputStream output = new BufferedOutputStream(fileOutput)
                byte[] buffer = new byte[BUFFER_SIZE]
                int read = 1

                while ((read = zipInput.read(buffer)) > 0) {
                    //write $read number of bytes from the buffer into the file
                    output.write(buffer, 0, read)
                }
                output.close()
            }
            catch (Exception e) {
                println ("[ERROR] + " + e.getMessage())
                System.exit(1)
            }
        }

        zipInput.closeEntry()
        zipEntry = zipInput.getNextEntry()
    }

    zipInput.close()
    return unzipped
}

def integrate = { version ->
    UUID result = null
    File tempDir = null
    String[] includes = ["**/*"] as String[]
    String[] excludes = [] as String[]
    try {
        System.out.println() // Put a new line in the output to separate versions

        boolean preserveExecutePermissions = Boolean.valueOf(props['saveFileExecuteBits'])
        List<String> versions = componentClient.getComponentVersions(componentName.toString(), false)
        boolean hasVersion = versions.contains(version)
        println ("[Info] Version from artifactory: " + version)
        if (!hasVersion) {
            println ("[Action] Creating new version")
            String versionId = versionClient.createVersion(componentName.toString(), version.toString(), " ").toString()
            if (isUseVFS) {
                if(!separator) {
                    separator = "."
                }
                def nugetPackage = props['nuGetPackage'] + separator + version + ".nupkg"
                println ("[Info] New version: " + nugetPackage)
                URI nupkgUri = new URI((repoUrl + REPO_PATH_SEPARATOR + repoName + REPO_PATH_SEPARATOR + nugetPackage).toString())
                tempDir = isNuget ? downloadFile(nupkgUri.toString(), version) : download(version)
                // If expecting to unzip, perform this action.  Otherwise add files to version now
                if (unzipArchive) {
                    for (File zipFile : tempDir.listFiles()) {
                        try {
                            if (zipFile.isDirectory()) {
                                println("[Info] Found directory " + zipFile.getName() + ", skipping directory, which is not an archived file.")
                            }
                            else {
                                String folderName = zipFile.getName().substring(0, zipFile.getName().lastIndexOf('.'))
                                File destDir = new File(tempDir, folderName)
                                boolean unzipped = unzipFile(zipFile, destDir)
                                if (unzipped) {
                                    println("[Info] File: '" + zipFile.getName() + "' unzipped, deleting archive...")
                                    zipFile.delete()
                                }
                            }
                        }
                        catch (Exception e) {
                            throw new Exception("Aborting: " + e.printStackTrace())
                        }
                    }
                }
                versionClient.addVersionFiles(componentName.toString(), versionId, tempDir, "", includes, excludes, preserveExecutePermissions, true, charset, extensions)
            } else {
                println (String.format("Not uploading version %s to VFS because using VFS was not selected.",
                        versionId))
            }
            setVersionProperties(version)
            apTool.setOutputProperty("VersionID", versionId)
        } else {
            println (String.format("UCD already contains version %s. A duplicate will not be created.",
                    version))
        }
    } catch (Exception e) {
        System.err.println(String.format("Error creating a new version: %s", e.toString()))
        result = null
    } finally {
        try {
            if (tempDir != null && tempDir.exists()) {
                IO.delete(tempDir)
            }
        } catch (IOException e) {
            System.err.println(String.format("Unable to delete download directory",
                    e.getMessage()))
        }
    }
    return result
}

try {
    if (! isNuget) {
        def latestVersions = getLatestVersions()
        for (version in latestVersions) {
            integrate(version)
        }
    } else {
        integrate(props['nuGetVersion'].toString())
    }
    System.out.println() // Put a new line in the output to separate the end of the imports
    apTool.storeOutputProperties()
    System.out.println("[Ok] Finished")
}
catch (Exception e) {
    e.printStackTrace()
    exitVal=1
}
System.exit(exitVal)
