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

import javax.net.ssl.SSLException
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.utils.HttpClientUtils
import org.apache.http.client.utils.URIBuilder
import org.apache.http.StatusLine

import com.urbancode.air.ExitCodeException
import com.urbancode.air.plugin.docker.Docker

public class ArtifactoryClient extends Docker {

    def namespace
    def repository
    Boolean reverseProxy = true

    public ArtifactoryClient(def props) {
        super(props)
        if (this.imageName.contains("/") && !this.imageName.endsWith("/")) {
            def temp = this.imageName.split("/", 2)
            this.namespace  = temp[0]
            this.repository = temp[1]
        }
        else {
            throw new Exception("[Error] Namespace not found in '${this.imageName}'.\n" +
                "Please specify it in the form 'namespace/repository' in the Image Name property.")
        }

        // Confirm registry does exist with the given information
        println "[Ok] Connecting to the Artifactory Docker registry..."
        pingRegistry()
        println "[Ok] Connected successfully."
        println ""
    }

    private def getBaseUri() {
        return new URIBuilder("${protocol}${registry}").build()
    }

    private def getCatalogUri() {
        URI result
        if (reverseProxy) {
            result = new URIBuilder(getBaseUri()).setPath("/v2/_catalog").build()
        } else {
            result = new URIBuilder(getBaseUri()).setPath("/artifactory/api/docker/${namespace}/v2/_catalog").build()
        }
        return result
    }

    private def getTagsUri() {
        URI result
        if (reverseProxy) {
            result = new URIBuilder(getBaseUri()).setPath("/v2/${repository}/tags/list").build()
        } else {
            result = new URIBuilder(getBaseUri()).setPath("/artifactory/api/docker/${namespace}/v2/${repository}/tags/list").build()
        }
        return result
    }

    private def getManifestsUri = { tag ->
        URI result
        if (reverseProxy) {
            result = new URIBuilder(getBaseUri()).setPath("/v2/${repository}/manifests/${tag}").build()
        } else {
            result = new URIBuilder(getBaseUri()).setPath("/artifactory/api/docker/${namespace}/v2/${repository}/manifests/${tag}").build()
        }
        return result
    }

    private void pingRegistry() {
        // Try connecting through reverse proxy - private registry
        CloseableHttpResponse response
        int status
        StatusLine statusLine
        String body = "";
        boolean repoFound = false

        // Try to connnect via a reverse proxy: /v2/_catalog
        // If successful, it may return a list of repositories. Check if given repo is in the list.
        // else try artifactory/api/docker/ endpoint, repeat
        try {
            response = requestWithAuth(getCatalogUri())
            status = response.statusLine.statusCode
            statusLine = response.statusLine
            body = response.entity?.content?.getText("UTF-8")
            repoFound = findRepository(body)
        }
        catch (SSLException ex) {
            println "[Error] Response from private registry server: ${ex.getMessage()}"
            // Continue and try to connect with insecure
        }
        catch (ExitCodeException ex) {
            println "[Warning] Attempting to connect using the `artifactory/api/docker` endpoint..."
        }
        finally {
            HttpClientUtils.closeQuietly(response)
        }

        if (status < 200 && status >= 300) {
            println "[Warning] Unexpected response from registry server:"
            println statusLine.toString()
            println body
        }

        // Try to connnect via a Artifactory Host: /artifactory/api/docker/{NAMESPACE}/v2/_catalog
        if (!repoFound) {
            try{
                reverseProxy = false
                response = requestWithAuth(getCatalogUri())
                status = response.statusLine.statusCode
                statusLine = response.statusLine
                body = response.entity?.content?.getText("UTF-8")
                if (status != 200) {
                    println "[Ok] Response from registry server:"
                    println statusLine.toString()
                    println body
                    throw new RuntimeException("[Error] Unable to ping registry using a reverse proxy or the /artifactory/api/docker endpoints. Please check credentials and make sure that registry supports v2 api.")
                } else {
                    repoFound = findRepository(body)
                }
            }
            catch (SSLException ex) {
                println "[Error] Response from registry server: ${ex.getMessage()}"
                throw ex
            }
            catch (Exception ex) {
                throw new Exception("[Error] Unable to connect to the Artifactory Docker registry: \n ${ex.getMessage()}")
            }
            finally {
                HttpClientUtils.closeQuietly(response)
            }
        }
        if (!repoFound) {
            throw new RuntimeException("[Error] Could not find the '${imageName}' image name at '${registry}'" +
                " using the above endpoints.\n"+
                "[Possible Solution] Verify the registry by placing these URLs in a browser. " +
                "The browser should list '${repository}' as an accessible repository in the response.")
        } else if (!reverseProxy) {
            println "[Warning] Connected to the registry using the Artifactory's hostname. " +
                "Consider using a reverse proxy for the Docker registry."
        }
    }

    private boolean findRepository(String jsonResponse) {
        boolean result = true
        if (jsonResponse.equals("") || jsonResponse == null) {
            result = false
        }
        else {

            def repoList

            try {
                 repoList = slurper.parseText(jsonResponse).repositories
            } catch (Exception ex) {
                ex.printStackTrace()
                throw new RuntimeException("[Error] Received non-JSON response from /v2/_catalog call: ${body}")
            }
            println "Repository List: " + repoList.toString()
            if (repoList.contains(imageName)) {
                println "Repository '${imageName}' is found at '${getCatalogUri()}'."
                repository = imageName
            } else if (repoList.contains(repository)) {
                println "Repository '${repository}' is found at '${getCatalogUri()}'."
            } else {
                println "[Warning] Repository '${imageName}' is not found at '${getCatalogUri()}'."
                 result = false             }
            println ""
        }
        return result
    }
}
