/**
 * Licensed Materials - Property of IBM* and/or HCL**
 * UrbanCode Deploy
 * (c) Copyright IBM Corporation 2017. All Rights Reserved.
 * (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 *
 * * Trademark of International Business Machines
 * ** Trademark of HCL Technologies Limited
 */

package com.urbancode.plugins.nuget

import com.urbancode.commons.httpcomponentsutil.CloseableHttpClientBuilder
import com.urbancode.commons.util.IO

import groovy.util.XmlSlurper
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.util.EntityUtils

import com.urbancode.plugins.nuget.NuGetVersion

public class NuGetHelper {

    final String URL_PATH_SEPARATOR  = "/"
    final String M_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
    final String D_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"

    String repoUrl
    String pkg
    String username
    String password

    String proxyHost
    int proxyPort
    String proxyUser
    String proxyPass
    boolean trustAllCerts

    CloseableHttpClient client
    private Map<String, File> tempDirs = new HashMap<String, File>()

    public NuGetHelper(String repoUrl, String pkg, String username, String password,
            String proxyHost, int proxyPort, String proxyUser, String proxyPass, boolean trustAllCerts) {

        while (repoUrl.endsWith(URL_PATH_SEPARATOR)) {
            repoUrl = repoUrl.substring(0, repoUrl.length() - 1)
        }

        this.repoUrl = repoUrl
        this.pkg = pkg
        this.username  = username
        this.password  = password
        this.proxyHost = proxyHost
        this.proxyPort = proxyPort
        this.proxyUser  = proxyUser
        this.proxyPass  = proxyPass
        this.trustAllCerts = trustAllCerts
        initClient()
    }


    /**
     * result Creates the class's CloseableHttpClient. Only called during class creation.
     */
    private void initClient() {
        if (!client) {
            CloseableHttpClientBuilder builder = new CloseableHttpClientBuilder()
            if (username) {
                builder.setPreemptiveAuthentication(true)
                builder.setUsername(username)
                builder.setPassword(password)
                if (proxyHost) {
                    builder.setProxyHost(proxyHost)
                    builder.setProxyPort(proxyPort)
                    if (proxyUser) {
                        builder.setProxyUsername(proxyUser)
                        builder.setProxyPassword(proxyPass)
                    }
                }
            }
            builder.setTrustAllCerts(trustAllCerts)
            client = builder.buildClient()
        }
    }

    /**
     * result Closes client opened by initClient()
     */
    public void closeClient() {
        if (client) {
            client.close()
        }
    }

    /**
     * return List of all NuGetVersions with class's package name
     */
    private List<NuGetVersion> getVersionList() {
        List<NuGetVersion> nvList = new ArrayList<NuGetVersion>()

        // Example: https://www.nuget.org/api/v2/Search()?searchterm='JSON.Net'&$select=Id,Version,Title
        // Note: Must escape $ sign
        String fullUrl = "${repoUrl}/Search()?searchTerm='${pkg}'&\$select=Id,Version,Title"
        URI versionListURI = new URI(fullUrl)
        HttpGet getRequest = new HttpGet(versionListURI.toString())
        getRequest.setHeader("Accept", "application/xml")
        CloseableHttpResponse response = null
        def fullXml
        try {
            response = client.execute(getRequest)
            int status = response.getStatusLine().getStatusCode()
            if (status == 200) {
                String responseStr = EntityUtils.toString(response.getEntity())
                XmlSlurper slurper = new XmlSlurper()
                fullXml = slurper.parseText(responseStr)
                    .declareNamespace(m:M_NAMESPACE)
                    .declareNamespace(d:D_NAMESPACE)
            }
            else if (status == 401) {
                println "[Info] " + response.getStatusLine()
                throw new Exception("[Error] Invalid credentials.")
            }
            else {
                println "[Info] " + response.getStatusLine()
                throw new Exception("[Error] Unexpected error reaching: " + versionListURI.toString())
            }

        } finally {
            if (response) {
                response.close()
            }
        }

        // Iterate through all entry values to find NuGet package versions
        // Run above example in Postman to see sample <entry> snippets
        fullXml.entry.each{ it ->
            // May retrieve many extra entries, confirm either a Title matches given Package name
            if (it.title == pkg || it.'m:properties'.'d:Title' == pkg || it.id == pkg) {
                NuGetVersion nv = new NuGetVersion();
                nv.id = it.id
                nv.pkgTitle = it.title
                nv.title = it.'m:properties'.'d:Title'
                nv.updated = it.updated
                nv.description = it.'m:properties'.'d:Description'
                nv.content = it.content.@src
                nv.version = it.'m:properties'.'d:Version'
                nvList.add(nv)
            }
        }

        // Only sort if there are more than 1 versions identified.
        if (nvList.size() > 1) {

            // Verify SimpleDateFormat
            SimpleDateFormat sdf = null
            List<String> possibleFormats = ["yyyy-mm-dd'T'hh:mm:ss.SSS'Z'", "yyyy-mm-dd'T'hh:mm:ss'Z'"]
            for(int i = 0; i < possibleFormats.size(); i++) {
                SimpleDateFormat tempFormat = new SimpleDateFormat(possibleFormats[i])
                try {
                    // Assuming first nuget version's updated date format matches all nuget versions.
                    tempFormat.parse(nvList[0].updated)
                    sdf = tempFormat
                    break
                } catch (ParseException ex) {
                    // Ignore Exception. If caught, then incorrect format indentified, so try next one.
                }
            }

            // If found sort, else alert unable to sort
            if (sdf) {
                // Sort by date
                nvList.sort{x,y ->
                    sdf.parse(x.updated) <=> sdf.parse(y.updated)
                }
            } else {
                println "[Warn] Unable to identify date format to sort NuGet versions. Example: ${nvList[0].updated}"
            }
        }

        return nvList
    }


    /**
     * param nv The NuGet Version with the .nupkg to download. Must have `content` value.
     *
     */
    public File download(NuGetVersion nv)
    throws IOException {
        if (nv.content) {
            // Example: https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3
            URI contentUri = new URI(nv.content)
            HttpGet getRequest = new HttpGet(contentUri.toString())
            CloseableHttpResponse response = null
            try {
                response = client.execute(getRequest)
                int status = response.getStatusLine().getStatusCode()
                if (status == 200) {
                    File exportNupkg = new File(getTempDir(nv.version), nv.getFilename())
                    IO.copy(response.getEntity().getContent(), exportNupkg) // Note: Folder must be deleted later.
                    println "[Info] Downloaded " + exportNupkg.getCanonicalPath()
                }
                else if (status == 401) {
                    println "[Info] " + response.getStatusLine()
                    throw new Exception("[Error] Invalid credentials.")
                }
                else {
                    println "[Info] " + response.getStatusLine()
                    throw new Exception("[Error] Unexpected error reaching: " + contentUri.toString())
                }
            } finally {
                if (response) {
                    response.close()
                }
            }
        } else {
            println "[Error] Download URL not found for given NuGet Version."
        }

        return getTempDir(nv.version)
    }

    /**
     * param version Version name to identify a folder created previously
     * result New/Original folder in the Agent's temp directory
     */
    private File getTempDir(String version)
    throws IOException {
        File result = tempDirs.get(version)
        if (result == null) {
            result = new File( System.getProperty("user.dir"), UUID.randomUUID().toString())
            IO.mkdirs(result)
            tempDirs.put(version, result)
        }
        return result
    }

}
