/**
 * 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 groovy.json.JsonSlurper
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
    boolean prerelease
    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, boolean prerelease, 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.prerelease = prerelease
        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> getVersionListV2Api() {
        List<NuGetVersion> nvList = new ArrayList<NuGetVersion>()

        // v2 Example: https://www.nuget.org/api/v2/Search()?searchterm='JSON.Net'&$select=Id,Version,Title&includePrerelease=true
        // Note: Must escape $ sign

        def fullXml
        String fullUrl = "${repoUrl}/Search()?searchTerm='${pkg}'&\$select=Id,Version,Title&includePrerelease='${prerelease}'"
        String nextPage = "next";
        while(nextPage != null && "next".equals(nextPage)) {
            println "fullUrl for http call is" + fullUrl
            CloseableHttpResponse response = null
            try {
                URI versionListURI = new URI(fullUrl)
                HttpGet getRequest = new HttpGet(versionListURI.toString())

                getRequest.setHeader("Accept", "application/xml")
                    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.link.each{ link ->
                if(link.@'rel' == 'next') {
                    fullUrl = link.@'href'
                    nextPage = 'next';  
                    println "new fullUrl from xml response is " + fullUrl
                } else {
                    nextPage = null;
                }               
            }

            fullXml.entry.each{ entry ->
                // May retrieve many extra entries, confirm either a Title matches given Package name
                if (entry.title == pkg.trim() || entry.'m:properties'.'d:Title' == pkg.trim() || entry.id == pkg.trim()) {
                    println "found a new qualifying entry"
                    NuGetVersion nv = new NuGetVersion();
                    nv.id = entry.id
                    nv.pkgTitle = entry.title
                    nv.title = entry.'m:properties'.'d:Title'
                    nv.updated = entry.updated
                    nv.description = entry.'m:properties'.'d:Description'
                    nv.content = entry.content.@src
                    nv.version = entry.'m:properties'.'d:Version'

                    if(prerelease==true) {
                        nvList.add(nv)
                    }
                    else {
                        if(entry.'m:properties'.'d:IsPrerelease' == false) {
                            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
    }

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

        //v3 format :GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}
        // Note: Must escape $ sign

        //v3 call example: https://api-v2v3search-0.nuget.org/query?q=NuGet.Versioning&prerelease=false

        String fullUrl = "${repoUrl}/query?q=${pkg}&prerelease=${prerelease}"

        URI versionListURI = new URI(fullUrl)
        HttpGet getRequest = new HttpGet(versionListURI.toString())
        getRequest.setHeader("Accept", "application/json")
        CloseableHttpResponse response = null
        def fullJson
        try {
            response = client.execute(getRequest)
            int status = response.getStatusLine().getStatusCode()
            if (status == 200) {
                String responseStr = EntityUtils.toString(response.getEntity())
                JsonSlurper slurper = new JsonSlurper()
                fullJson = slurper.parseText(responseStr)
            }
            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 data snippets

        fullJson.data.each{ data ->

            // May retrieve many extra entries, confirm either a Title matches given Package name

            if (data.id == pkg.trim()) {

                def description = data.description
                def id = data.id

                data.versions.each { ver->

                    NuGetVersion nv = new NuGetVersion()
                    nv.id = id
                    nv.pkgTitle = id
                    nv.title = id
                    nv.updated = ""
                    nv.description = description
                    nv.version = ver.version

                    String contentUrl = ver."@id"

                    URI contentURI = new URI(contentUrl)
                    HttpGet contentGetRequest = new HttpGet(contentURI.toString())
                    contentGetRequest.setHeader("Accept", "application/json")
                    CloseableHttpResponse contentResponse = null
                    def contentJson
                    try {
                        contentResponse = client.execute(contentGetRequest)
                        int status = contentResponse.getStatusLine().getStatusCode()
                        if (status == 200) {
                            String contentResponseStr = EntityUtils.toString(contentResponse.getEntity())

                            JsonSlurper contentSlurper = new JsonSlurper()
                            contentJson = contentSlurper.parseText(contentResponseStr)
                        }
                        else if (status == 401) {
                            println "[Info] " + contentResponse.getStatusLine()
                            throw new Exception("[Error] Invalid credentials.")
                        }
                        else {
                            println "[Info] " + contentResponse.getStatusLine()
                            throw new Exception("[Error] Unexpected error reaching: " + contentURI.toString())
                        }

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

                    nv.content = contentJson.packageContent
                    nvList.add(nv)
                }
            }
        }

        return nvList
    }


    private List<NuGetVersion> getVersionList() {
        List<NuGetVersion> nvList = new ArrayList<NuGetVersion>()

        String repoRegex = /v3/

        def matchRepo = this.repoUrl =~ repoRegex
        boolean found = true
        println "Repo url used : ${this.repoUrl}"
        if(matchRepo.count>0) {
            println "Repo Api version : v3"
            return getVersionListV3Api()

        }else {
            println "Repo Api version : v2"
            return getVersionListV2Api()

        }

    }
    /**
     * 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
    }

}
