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

package com.urbancode.air.plugin.artifactory

import groovy.json.JsonException
import groovy.json.JsonSlurper

import java.security.NoSuchAlgorithmException

import org.apache.commons.lang.ObjectUtils
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpPut
import org.apache.http.client.methods.HttpRequestBase
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
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.commons.fileutils.digest.DigestUtil
import com.urbancode.commons.fileutils.FileUtils
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO


public class ArtifactoryHelper {
    private DefaultHttpClient client
    private String apiKey

    public ArtifactoryHelper(username, password, apiKey) {
        if (apiKey) {
            this.apiKey = apiKey
        }
        HttpClientBuilder builder = new HttpClientBuilder()
        if (!apiKey && username && password) {
            builder.setPreemptiveAuthentication(true)
            builder.setUsername(username)
            builder.setPassword(password)
        }
        client = builder.buildClient()
    }

    public void verifyHash (File fileToVerify, storedDigest) {
        final ARTIFACT_FILE_HASH_ALGORITHM = "sha1"
        if (storedDigest != null) {
            String computedDigest;
            try {
                computedDigest = DigestUtil.getHexDigest(fileToVerify, ARTIFACT_FILE_HASH_ALGORITHM);
                if (!ObjectUtils.equals(storedDigest, computedDigest)) {
                    throw new Exception("[Error] Artifact file verification of ${fileToVerify.getName()} failed. Expected "
                    + " digest of ${storedDigest} but the downloaded file was ${computedDigest}");
                }
            }
            catch (NoSuchAlgorithmException e) {
                throw new Exception("[Error] Algorithm to verify Maven remote artifacts not supported: " +
                ARTIFACT_FILE_HASH_ALGORITHM);
            }
            catch (IOException e) {
                throw new Exception("[Error] Error verifying downloaded Maven remote artifacts: ${e.getMessage()}", e);
            }
        }
    }


    public File downloadFileFromRepo(String url, String checkHash) {
        def response = executeHttpRequest(url, new HttpGet(), null)
        if (!response) {
            throw new Exception("[Error] Could not find file ${url} in artifactory: ${response}");
        }
        int status = response.getStatusLine().getStatusCode();
        if (status == HttpStatus.SC_OK) {
            def jsonString = EntityUtils.toString(response.getEntity());
            def slurper = new JsonSlurper();
            def infoJSON = slurper.parseText(jsonString);
            def checksumMap = infoJSON.checksums;
            def downloadUrl = infoJSON.downloadUri;
            System.out.println("[Action] Downloading: ${downloadUrl}");
            response = executeHttpRequest(downloadUrl, new HttpGet(), null)
            if (!response) {
                throw new Exception("[Error] Could not download file : ${downloadUrl}\nResponse : ${response}");
            }
            status = response.getStatusLine().getStatusCode();
            if (status == HttpStatus.SC_OK) {
                String tempFileSuffix = ".maven2";
                int extIndex = url.lastIndexOf(".");
                if (extIndex >= 0) {
                    tempFileSuffix = url.substring(extIndex);
                }
                File artifactFile = File.createTempFile("maven2-", tempFileSuffix);
                FileUtils.writeInputToFile(response.getEntity().getContent(), artifactFile);
                if (checkHash && Boolean.valueOf(checkHash)) {
                    //verify checksum
                    verifyHash(artifactFile, checksumMap.sha1);
                    System.out.println("[Ok] Verification for file : ${artifactFile} succeeded!");
                }
                return artifactFile;
            } else {
                throw new Exception("[Error] Exception downloading file : ${downloadUrl}\nErrorCode : ${status.toString()}");
            }
        }
    }

    /**
    *  @param base The base URL of the Artifactory Server
    *  @param path The path to append to the base
    *  @return The combined base and path URL
    */
    public String getCombinedUrl(String base, String path) {
        if (base.endsWith('/')) {
            base = base.substring(0, base.length - 1)
        }
        if (path.startsWith('/')) {
            path = path.substring(1)
        }
        return base + '/' + path
    }

    /**
    *  @param url The url to send the POST request
    *  @param json The JSON payload to add to the request
    */
    public def jsonPost(String url, String json) {
        StringEntity jsonEntity
        try  {
            jsonEntity = new StringEntity(json, ContentType.APPLICATION_JSON)
        }
        catch (IllegalArgumentException e) {
            println ("[Error] Could not create StringEntity for POST: ${e.getMessage()}")
            println ("POST data: ${json}.replace('\n', '')")
            return false
        }
        catch (UnsupportedEncodingException e) {
            println ("[Error] Could not create StringEntity for POST: ${e.getMessage()}")
            println ("POST data: ${json}.replace('\n', '')")
            return false
        }
        def response = executeHttpRequest(url, new HttpPost(), jsonEntity)
        if (!response) {
            println ("[Error] Could not execute POST: ${response}")
            return false
        }
        int statusCode = response.getStatusLine().getStatusCode()
        String responseText = response.entity?.content?.text
        printMessages(responseText, 'ERROR')
        printMessages(responseText, 'INFO')
        if (statusCode != HttpStatus.SC_OK) {
            println ("[Error] Bad response from server: ${statusCode} - ${responseText.replace('\n', '')}")
            return false
        }
        return responseText
    }

    /**
    *  @param url The URL to execute the HTTP PUT against
    *  @return True if the put operation is successful, false otherwise
    */
    public boolean put(String url) {
        def response = executeHttpRequest(url, new HttpPut(), null)
        if (!response) {
            return false
        }
        def statusCode = response.getStatusLine().getStatusCode()
        def text = response.entity?.content?.text
        if (statusCode != HttpStatus.SC_NO_CONTENT) {
            println ("[Error] Received bad status from Artifactory: ${statusCode} - ${text}")
            return false
        }
        else {
            return true
        }
    }

    /**
    *  @param url The url of the artifact to download
    *  @param workDir The working directory to download the artifact to
    *  @return True if the artifact is downloaded successfully, false otherwise
    */
    public boolean download(String url, def workDir) {
        println ("[Action] Downloading ${url} to temporary file...")
        def response = executeHttpRequest(url, new HttpGet(), null)
        if (!response) {
            return false
        }
        def statusCode = response.getStatusLine().getStatusCode()
        if (statusCode != HttpStatus.SC_OK) {
            println ("[Error] Unable to download artifacts. Response from Artifactory: ${statusCode} - ${response}")
            return false
        }
        String tempFileSuffix = ".tmp"
        int extIndex = url.lastIndexOf(".")
        if (extIndex >= 0) {
            tempFileSuffix = url.substring(extIndex)
        }
        String fileName
        // attempt to get file name from response header, if not present just get it from url
        def headerValues = response.getFirstHeader('Content-Disposition').getValue().split(';')
        String searchTerm = ' filename='
        headerValues.each { value ->
            if (value.startsWith(searchTerm)) {
                fileName = value.substring(searchTerm.length() + 1, value.length() - 1)
            }
        }
        if (!fileName) {
            String[] filePath = url.split('/')
            fileName = filePath[filePath.length - 1]
        }
        return downloadAndMoveFile(response, tempFileSuffix, fileName, workDir)
    }

    /**
    *  @param url The url of the download archive endpoint
    *  @param postInfo the json string containing the info to send to the download endpoint
    *  @param suffix The suffix for the temporary file
    *  @param fileName The name to download the final file as
    *  @param workDir The working directory to download the file in
    *  @return True if the artifact is downloaded successfully, false otherwise
    */
    public boolean downloadArchive(String url, String postInfo, String suffix, String fileName, def workDir) {
        println ("[Action] Downloading archive from ${url} with payload ${postInfo.replace('\n', '')} to temporary file...")
        StringEntity jsonEntity
        try  {
            jsonEntity = new StringEntity(postInfo, ContentType.APPLICATION_JSON)
        }
        catch (IllegalArgumentException e) {
            println ("[Error] Could not create StringEntity for POST: ${e.getMessage()}")
            println ("POST data: ${postInfo}.replace('\n', '')")
            return false
        }
        catch (UnsupportedEncodingException e) {
            println ("[Error] Could not create StringEntity for POST: ${e.getMessage()}")
            println ("POST data: ${postInfo}.replace('\n', '')")
            return false
        }
        def response = executeHttpRequest(url, new HttpPost(), jsonEntity)
        if (!response) {
            println ("[Error] Could not execute POST")
            return false
        }
        return downloadAndMoveFile(response, suffix, fileName, workDir)
    }

    /**
    *  @param url The HTTP response to download the file from
    *  @param tmpSuffix The suffix for the temporary file
    *  @param fileName The name to download the final file as
    *  @param workDir The working directory to download the file in
    *  @return True if the artifact is downloaded successfully, false otherwise
    */
    private boolean downloadAndMoveFile(def response, String tmpSuffix, String fileName, def workDir) {
        File tempFile = File.createTempFile("ArtifactoryDownload-", tmpSuffix)
        tempFile.deleteOnExit()
        try {
            FileUtils.writeInputToFile(response.getEntity().getContent(), tempFile)
        }
        catch (IOException e) {
            println ("[Error] Failed to download artifact to temporary file: ${e.getMessage()}")
            return false
        }
        println ("[Ok] Downloaded to temporary file ${tempFile.getAbsolutePath()}")
        File finalFile = new File(workDir, "."+fileName)
        println ("[Action] Moving downloaded artifact to: ${finalFile.getAbsolutePath()}")
        try {
            IO.move(tempFile, finalFile)
        }
        catch (IOException e) {
            println ("[Error] Failed to move downloaded temporary file: ${e.getMessage()}")
            return false
        }
        println ('[Ok] Artifact moved.')
        return true
    }

    /**
    *  @param uri The URI of the HTTP Request to make
    *  @param request HTTP request to make
    *  @param postInfo The information to set as an entity for an HTTP POST/PUT
    *  @return The response received from executing the request
    */
    private def executeHttpRequest(String uri, HttpRequestBase request, def postInfo) {
        try {
            request.setURI(encodeUri(uri))
        }
        catch (IllegalArgumentException e) {
            println ("[Error] Invalid URL: ${e.getMessage()}.")
            println ('[Possible Solution] Please update the step configuration with a valid URL.')
            return false
        }
        if (postInfo) {
            try {
                request.setEntity(postInfo)
            }
            catch (IOException e) {
                println ("[Error] Could not set entity: ${e.getMessage()}.")
                return false
            }
        }
        if (apiKey) {
            request.setHeader('X-JFrog-Art-API', apiKey)
        }
        HttpResponse response
        try {
            response = client.execute(request)
        }
        catch (IOException e) {
            println ("[Error] Problem executing HTTP request: ${e.getMessage()}.")
            println ("Response: ${response}")
            return false
        }
        return response
    }

    /**
    *  @param response The response to parse for messages
    *  @param level The level of messages to print out
    */
    private void printMessages(String text, String level) {
        try {
            def messages = new JsonSlurper().parseText(text)."messages"
            if (messages instanceof ArrayList && messages.size() > 0) {
                messages.each { message ->
                    if (message.level.equals(level)) {
                        println ("[${level.toLowerCase()}] ${message.message}")
                    }
                }
            }
        }
        catch (JsonException jse) {
            println ("[Error] Returned JSON is not well-formed: ${jse.getMessage()}")
            println ("JSON: ${text.replace('\n', '')}")
        }
    }

    private URI encodeUri(String uri) {
        if (!uri.startsWith('http://') && !uri.startsWith('https://')) {
            uri = 'https://' + uri
        }
        URL rawUri = new URL(uri)
        return new URI(rawUri.getProtocol(),
                       rawUri.getAuthority(),
                       rawUri.getPath(),
                       rawUri.getQuery(),
                       rawUri.getRef())
    }
}
