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

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.util.Node
import groovy.util.XmlParser

import java.nio.charset.Charset
import java.util.HashMap
import java.util.Map
import java.util.UUID

import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.util.EntityUtils

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

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

def apTool = new AirPluginTool(this.args[0], this.args[1])
def inProps = apTool.getStepProperties()

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

final String[] API_QUERY = ['', 'nuget', 'api/v2']
final String[] API_DOWNLOAD = ['','download', 'api/v1/package', 'api/v2/package']
int API_VERSION = null

def exitVal = 0

def nuGetHost = inProps['nuGetHost']
def nuGetPackage = inProps['nuGetPackage']
def username = inProps['user']
def password = inProps['password']
def isUseVFS = inProps['isUseVFS']
def componentName = inProps['componentName']
def saveFileExecuteBits = inProps['saveFileExecuteBits']
def nuGetVersion = inProps['version']

while (nuGetHost.endsWith('/')) {
    nuGetHost = nuGetHost.substring(0, nuGetHost.length() - 1);
}

def PACKAGE_ID = null

def extensionsString = inProps['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()
    }
}

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

String webString = System.getenv('AH_WEB_URL')
URI webUrl = new URI(webString)

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

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

def getResponse = { url, json ->
    println ('HTTP GET url: ' + url)
    HttpGet get = new HttpGet(url)
    if (json) {
        get.addHeader('accept', 'application/json')
    }
    try {
        return client.execute(get)
    }
    catch (IOException ioe) {
        return false
    }
}

def setApiVersion = {
    println ('Attempting to determine NuGet repository REST API endpoint...')
    for (int i=0; i < API_QUERY.length; i++) {
        String url = nuGetHost + '/' + API_QUERY[i]
        def response = getResponse(url, true)
        if (response && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            println ('NuGet REST API endpoint found: /' + API_QUERY[i] + '/')
            API_VERSION = i
            break
        }
    }
    if (API_VERSION == null) {
        throw new Exception('Could not reach NuGet Server to detect Api version')
    }
}

def xmlToJson = { response ->
    Node root = new XmlParser().parseText(response)
    def list = []
    root.entry.each { entry ->
        String id = entry.'m:properties'.'d:Id'[0]?.value()?.getAt(0)?.toString()
        String version = entry.'m:properties'.'d:Version'[0].value()[0].toString()
        String title = entry.'m:properties'.'d:Title'[0].value()[0].toString()
        if (!id) {
            // No id is given from Nexus API calls. Nexus specific work around.
            id = title
        }
        list.add(['Id': id, 'Version': version, 'Title': title])
    }
    JsonBuilder builder = new JsonBuilder(list)
    return new JsonSlurper().parseText(builder.toString())
}

def getJsonResults = { response ->
    if (response.substring(0, 1).equals('<')) {
        println 'Detected return type of XML insead of JSON. Attempting to convert XML.'
        return xmlToJson(response)
    }
    else {
        def jsonArray = new JsonSlurper().parseText(response)
        return jsonArray.d.results
    }
}

def setPackageId = {
    println ('\nAttempting to determine NuGet package ID...')
    boolean found = false
    URIBuilder uri = new URIBuilder(nuGetHost + '/' + API_QUERY[API_VERSION] + '/' + 'Search()')
    uri.setParameter('searchTerm', "'" + nuGetPackage + "'")
    uri.setParameter('targetFramework', '')
    String url = uri.toString()
    url += '&$select=Id,Version,Title'
    int status = 0
    HttpResponse response = getResponse(url, true)
    if (response) {
        status = response.getStatusLine().getStatusCode()
        if (status == HttpStatus.SC_BAD_REQUEST) {
            println ('Attempting again with includePrerelease...')
            url += '&includePrerelease=false'
            response = getResponse(url, true)
            if (response) {
                status = response.getStatusLine().getStatusCode()
            }
        }
    }
    if (status == HttpStatus.SC_OK) {
        def results = null
        results = getJsonResults(response.entity?.content?.getText("UTF-8"))
        results.each {
            if (it.Title == nuGetPackage) {
                found = true
                PACKAGE_ID = it.Id
            }
        }
    }
    if (found) {
        println ('NuGet package ID found: ' + PACKAGE_ID)
    }
    else {
        println ('Could not determine NuGet package ID')
    }
}

def getPackageJson = { byId, latest ->
    URIBuilder uri
    if (byId) {
        uri = new URIBuilder(nuGetHost + '/' + API_QUERY[API_VERSION] + '/FindPackagesById()')
        uri.setParameter('id', "'" + PACKAGE_ID + "'")
    }
    else {
        uri = new URIBuilder(nuGetHost + '/' + API_QUERY[API_VERSION] + '/Search()')
        uri.setParameter('searchTerm', "'" + nuGetPackage + "'")
    }
    String url = uri.toString()
    url += '&$select=Id,Version,Title'
    if (latest) {
        url += '&$filter=IsLatestVersion'
    }
    HttpResponse response = getResponse(url, true)
    int status = 0
    if (response) {
        status = response.getStatusLine().getStatusCode()
    }
    if (status == HttpStatus.SC_OK) {
        return getJsonResults(response.entity?.content?.getText("UTF-8"))
    }
    else {
        return false
    }
}

def versionAvailable = { ->
    def packageJson = getPackageJson(true, false)
    if (!packageJson) {
        println ('Attempting package retrieval again with broader search...')
        packageJson = getPackageJson(false, false)
    }
    boolean found = false
    packageJson.each {
        if (it.Id == PACKAGE_ID && it.Version == nuGetVersion) {
            found = true
        }
    }
    return found
}

def getLatestVersion = {
    def version = null
    def packageJson = getPackageJson(true, true)
    if (!packageJson) {
        println ('Attempting package retrieval again with broader search...')
        packageJson = getPackageJson(false, true)
    }
    packageJson.each {
        if (it?.Id == PACKAGE_ID) {
            version = it.Version
        }
    }
    if (version == null) {
        println ('Error: Could not find latest version available')
    }
    else {
        return version
    }
}

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

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

def downloadFile = { version ->
    boolean downloaded = false
    for (i = 0; i < API_DOWNLOAD.length; i++) {
        String nuGetFile = (nuGetHost + '/' + API_DOWNLOAD[i] + '/' + PACKAGE_ID + '/' + version).toString()
        println ('Attempting download type ' + (i+1) + '/' + API_DOWNLOAD.length)
        try {
            HttpResponse response = getResponse(nuGetFile, false)
            int status = response.getStatusLine().getStatusCode()
            if (status == HttpStatus.SC_OK) {
                downloaded = true
                File file = new File(getTempDir(version), "${PACKAGE_ID}.${version}.nupkg")
                IO.copy(response.getEntity().getContent(), file)
                return getTempDir(version)
            }
        }
        catch (Exception e) {
            println ('Error downloading file')
        }
    }
    if (!downloaded) {
        throw new Exception('Exception downloading files: Could not find NuGet Server download location')
    }
}

def downloadPackage = { version ->
    println ('\nAttempting to download NuGet package...')
    File tempDir = null
    String[] includes = ['**/*'] as String[]
    String[] excludes = [] as String[]
    try {
        boolean preserveExecutePermissions = Boolean.valueOf(inProps['saveFileExecuteBits'])
        versions = componentClient.getComponentVersions(componentName.toString(), false)
        hasVersion = versions.contains(version)
        if (!hasVersion) {
            println ('Creating new version of package in UCD')
            String versionId
            if (isUseVFS) {
                tempDir = downloadFile(version)
                versionId = versionClient.createVersion(componentName.toString(), version.toString(), ' ').toString()
                versionClient.addVersionFiles(componentName.toString(), versionId, tempDir, '', includes, excludes, preserveExecutePermissions, true, charset, extensions)
            } else {
                versionId = versionClient.createVersion(componentName.toString(), version.toString(), ' ').toString()
                println (String.format('Not uploading version %s to VFS because using VFS was not selected.',
                        versionId))
            }
            apTool.setOutputProperty("VersionID", versionId)
            println "Successfully imported NuGet ${nuGetPackage} ${version}!"
        } 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.getMessage()))
    }
    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()))
        }
    }
}

try {
    setApiVersion()
    setPackageId()
    println ('\nAttempting to find NuGet package version...')
    if (nuGetVersion == null || nuGetVersion == '') {
        println ('No NuGet package version specified, downloading latest available')
        nuGetVersion = getLatestVersion()
    }
    else {
        if (!versionAvailable()) {
            throw new Exception('NuGet package version specified does not exist on server')
        }
    }
    println ('NuGet package version found: ' + nuGetVersion)
    downloadPackage(nuGetVersion)
} finally {
    apTool.setOutputProperties()
}
