/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * (c) Copyright IBM Corporation 2017. 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 java.io.File

import groovy.json.JsonSlurper

import java.security.NoSuchAlgorithmException

import org.apache.commons.lang.ObjectUtils
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPut
import org.apache.http.entity.StringEntity
import org.apache.http.entity.FileEntity
import org.apache.http.impl.client.CloseableHttpClient

import org.jfrog.artifactory.client.Artifactory
import org.jfrog.artifactory.client.ArtifactoryClient
import org.jfrog.artifactory.client.UploadableArtifact

import com.urbancode.commons.fileutils.FileUtils
import com.urbancode.commons.fileutils.AntStylePatternMatcher
import com.urbancode.commons.fileutils.digest.DigestUtil
import com.urbancode.commons.httpcomponentsutil.CloseableHttpClientBuilder
import com.urbancode.commons.util.IO

public class ArtifactoryHelper {

    private final CloseableHttpClientBuilder builder = new CloseableHttpClientBuilder()
    private final ARTIFACT_FILE_HASH_ALGORITHM = "sha1"

    private String username
    private String password

    private List<File> findFiles(File baseDir, List<String> includes, List<String> excludes) {
        if ((!baseDir.exists()) || baseDir.isFile()) {
            throw new Exception("The base directory '" + baseDir.getPath() + "' does not exist or is a file")
        }
        // FileUtils#getFileArray trims the baseDir part when returning data
        String[] filePaths = FileUtils.getFileArray(baseDir, [] as String[], [] as String[])
        String[] includesArray = includes as String[]
        String[] excludesArray = excludes as String[]
        AntStylePatternMatcher antMatcher = new AntStylePatternMatcher(includesArray, excludesArray)
        List<File> matchingFiles = new ArrayList<File>()
        for (String filePath : filePaths) {
            filePath = trimLeadingSlashes(filePath)
            File file = new File(baseDir, filePath)
            File withoutBaseFile = new File(filePath)
            if (antMatcher.accept(file.getPath())) {
                println "Path $withoutBaseFile.path matches the include/exclude patterns"
                matchingFiles.add(withoutBaseFile)
            }
            else {
                println "Path $withoutBaseFile.path does not match the include/exclude patterns. Skipping"
            }
        }
        return matchingFiles
    }

    private boolean verifyChecksum(File fileToVerify, String checksum) {
        if (checksum) {
            String computedDigest
            try {
                computedDigest = DigestUtil.getHexDigest(fileToVerify, ARTIFACT_FILE_HASH_ALGORITHM)
                return ObjectUtils.equals(checksum, computedDigest)
            }
            catch (NoSuchAlgorithmException e) {
                throw new Exception("Algorithm not supported: " + ARTIFACT_FILE_HASH_ALGORITHM)
            }
            catch (IOException e) {
                throw new Exception("Error verifying downloaded artifact: " +
                e.getMessage(), e)
            }
        }
    }
    //----------------------------------------------------------------------------------------------

    public ArtifactoryHelper(String username, String password) {
        this.username = username
        this.password = password
    }

    public CloseableHttpClient createClient() {
        builder.setUsername(username)
        builder.setPassword(password)
        builder.setPreemptiveAuthentication(true)
        CloseableHttpClient client = builder.buildClient()
        return client
    }

    public File downloadFileFromRepo(CloseableHttpClient client,
                                     String repoUrl, String repoName, File baseDir, String targetPath,
                                     Boolean verifyHash) {
        String downloadUrl = repoUrl + "/" + repoName + "/" + targetPath
        HttpGet get = new HttpGet(downloadUrl)
        HttpResponse response = client.execute(get)
        int status = response.getStatusLine().getStatusCode()
        if (status != HttpStatus.SC_OK) {
            throw new Exception("Exception downloading file : " + downloadUrl + "\nErrorCode : " + status.toString());
        }
        File tempArtifactFile
        File targetFile
        try {
            tempArtifactFile = File.createTempFile("temp", null)
            tempArtifactFile << response.entity.content
            if (verifyHash) {
                String fileInfoUrl = repoUrl + "/api/storage/" + repoName + "/" + targetPath
                HttpGet fileInfoGet = new HttpGet(fileInfoUrl)
                HttpResponse fileInfoRes = client.execute(fileInfoGet)
                int fileInfoStatus = response.getStatusLine().getStatusCode()
                if (fileInfoStatus != HttpStatus.SC_OK) {
                    throw new Exception("Exception downloading file info from $fileInfoUrl: Response: "
                        + status.toString())
                }
                String fileInfoEntity = fileInfoRes.entity.content.text
                JsonSlurper slurper = new JsonSlurper()
                def fileInfoResult = slurper.parseText(fileInfoEntity)
                def sha1Sum = fileInfoResult?.checksums?.sha1
                if (!sha1Sum) {
                    throw new Exception("The response entity does not seem to have a sha1 checksum:\n$fileInfoEntity")
                }
                if (!verifyChecksum(tempArtifactFile, sha1Sum)) {
                    throw new Exception("Verification for file : " + tempArtifactFile + " failed")
                }
                println("Verification for file " + tempArtifactFile + " succeeded")
            }
            targetFile = new File(baseDir, targetPath)
            IO.mkdirs(targetFile.getParentFile())
            IO.move(tempArtifactFile, targetFile)
        }
        finally {
            if (tempArtifactFile != null && tempArtifactFile.exists()) {
                IO.delete(tempArtifactFile)
            }
        }
        return targetFile
    }

    public void uploadFileToRepo(CloseableHttpClient client,
                                 String baseUrl, String repoName, String targetPath, File baseDir,
                                 List<String> includes, List<String> excludes, Boolean preserveDirs,
                                 Properties properties) {
        List<File> fileList = findFiles(baseDir, includes, excludes);
        println "Found " + fileList.size() + " file(s) matching the include/exclude patterns";
        for (File file : fileList) {
            String artifactUploadPath = targetPath
            if (preserveDirs) {
                artifactUploadPath += "/" + file.getPath()
            }
            else {
                artifactUploadPath += "/" + file.getName()
            }
            artifactUploadPath = trimLeadingSlashes(artifactUploadPath)
            println "Uploading file '" + file.getPath() + "' to path '" + artifactUploadPath + "'"
            Artifactory artifactory = ArtifactoryClient.create(baseUrl, username, password)
            UploadableArtifact uploadable = artifactory.repository(repoName)
                    .upload(artifactUploadPath, file)
            for (String key : properties.keySet()) {
                String value = properties.getProperty(key);
                println "Adding property to artifact: '$key'='$value'"
                uploadable.withProperty(key, value)
            }
            org.jfrog.artifactory.client.model.File result = uploadable.doUpload()
            println "Upload done. Download url: " + result.getDownloadUri()
        }
    }

    public void uploadBuildInfoJson(CloseableHttpClient client, String repoUrl, String buildInfo) {
        repoUrl = trimTrailingSlashes(repoUrl)
        String url = repoUrl + "/api/build/"
        HttpPut request = new HttpPut(url)
        StringEntity buildInfoEntity = new StringEntity(buildInfo)
        buildInfoEntity.setContentType("application/json")
        request.addHeader("Content-Type", "application/json")
        request.setEntity(buildInfoEntity)
        HttpResponse response = client.execute(request)
        int responseCode = response.getStatusLine().getStatusCode()
        if (responseCode == HttpStatus.SC_OK || responseCode == 204) {
            println "Build info has been successfully uploaded"
        }
        else {
            println response
            throw new Exception("Failed to upload build information: Error " + responseCode.toString()
                + "\nMessage: " + response?.getEntity()?.getContent()?.text)
        }
    }

    public String trimTrailingSlashes(String str) {
        return str.replaceAll("/+\$", "")
    }

    public String trimLeadingSlashes(String str) {
        return str.replaceAll("^/+", "")
    }
}
