/**
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * (c) Copyright IBM Corporation 2011, 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.docker

import groovy.json.JsonSlurper
import java.net.URI
import java.util.regex.Matcher
import java.util.regex.Pattern
import javax.net.ssl.SSLException
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.impl.client.DefaultHttpClient

import com.urbancode.air.AirPluginTool
import com.urbancode.air.XTrustProvider
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

public class RegistryClient {

    def registry
    def username
    def password
    def proxyHost
    def proxyPortString
    def proxyUsername
    def proxyPassword
    def slurper
    def client
    def imageName
    def allowInsecure
    def protocol

    public RegistryClient( def props) {
        this.registry = props['dockerRegistryName']
        this.username = props['dockerRegistryUsername']
        this.password = props['dockerRegistryPassword']
        this.imageName = props['dockerImageName']
        this.allowInsecure = props['allowInsecure'].toBoolean()

        this.proxyHost = props['proxyHost']
        this.proxyPortString = props['proxyPort']
        this.proxyUsername = props['proxyUsername']
        this.proxyPassword = props['proxyPassword']

        this.protocol = "https://"

        this.slurper = new JsonSlurper()

        HttpClientBuilder builder = new HttpClientBuilder()
        if (username) {
            builder.setPreemptiveAuthentication(true)
            builder.setUsername(username)
            builder.setPassword(password)
        }
        if (proxyHost) {
            def proxyPort = Integer.parseInt(proxyPortString)
            builder.setProxyHost(proxyHost)
            builder.setProxyPort(proxyPort)
            if (proxyUsername) {
                builder.setProxyUsername(proxyUsername)
                builder.setProxyPassword(proxyPassword)
            }
        }

        if (allowInsecure) {
            XTrustProvider.install()
            builder.setTrustAllCerts(true)
        }

        this.client = builder.buildClient()

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

    def parseAuthHeader = { def header ->
        def result = new Expando()
        Pattern p = Pattern.compile("(\\S+)=(\\S+)")
        String [] chunks = header.value.split("[,\\s]+");

        chunks.each { chunk ->
            Matcher m = p.matcher(chunk)
            if (m.matches())
            {
                if (m.group(1).equalsIgnoreCase("realm")) {
                    result.realm = m.group(2).replaceAll("\\\"", "")
                }
                else if (m.group(1).equalsIgnoreCase("service")) {
                    result.service = m.group(2).replaceAll("\\\"", "")
                }
                else if (m.group(1).equalsIgnoreCase("scope")) {
                    result.scope = m.group(2).replaceAll("\\\"", "")
                }
            }
        }

        return result
    }

    def getToken = { def authHeader ->
        def result = null
        def authConfig = parseAuthHeader(authHeader)
        def uriBuilder = new URIBuilder(authConfig.realm)

        if (authConfig.scope) {
            uriBuilder.setParameter("scope", authConfig.scope)
        }
        if (authConfig.service) {
            uriBuilder.setParameter("service", authConfig.service)
        }

        def tokenUri = uriBuilder.build()

        HttpGet tokenRequest = new HttpGet(tokenUri)
        def tokenResponse = client.execute(tokenRequest)

        def responseBody = tokenResponse.entity.content.text
        result = slurper.parseText(responseBody).token
        return result
    }

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

    def getPingUri() {
        return new URIBuilder(getBaseUri()).setPath("/v2").build()
    }

    def getTagsUri() {
        return new URIBuilder(getBaseUri()).setPath("/v2/${imageName}/tags/list").build()
    }

    def getManifestsUri = { tag ->
        return new URIBuilder(getBaseUri()).setPath("/v2/${imageName}/manifests/${tag}").build()
    }

    void pingRegistry() {
        // Assumes using protocol of https://
        def response
        def status
        try {
            response = requestWithAuth(getPingUri())
            status = response.statusLine.statusCode
        }
        catch (SSLException ex) {
            println "[Error] Response from registry server: ${ex.getMessage()}"

        }
        if (status != 200) {
            if (status) {
                println "[Warning] Unexpected response from registry server:"
                println response.statusLine
                println response.entity?.content?.text
            }

            // Try using http:// instead
            if (allowInsecure) {
                println "[Action] Attempting to ping registry using http protocol..."
                this.protocol = "http://"
                response = requestWithAuth(getPingUri())
                status = response.statusLine.statusCode
                if (status != 200) {
                    println "[Ok] Response from registry server:"
                    println response.statusLine
                    println response.entity?.content?.text
                    throw new RuntimeException("[Error] Unable to ping registry using http and https protocols. Please check credentials and make sure that registry supports v2 api.")
                }
                println "[Warning] Connected to registry using the unsecure http protocol."
            }
            else {
                println "[Possible Solution] Allow Insecure to attept the connection over http protocol"
                System.exit(1)
            }
        }
    }

    public def getTags() {
        def tags = []
        def tagsJson
        println "[Action] Requesting list of tags for ${imageName}..."
        tagsJson = requestWithAuth(getTagsUri()).entity?.content?.text
        tags = slurper.parseText(tagsJson).tags

        if (tags) {
            println "[Ok] Retrieved ${tags?.size()} tag(s)."
            println ""
        } else {
            println "No tags found"
        }

        return tags
    }

    private def getManifest = { def tag ->
        def result
        def manifestJson = requestWithAuth(getManifestsUri(tag)).entity?.content?.text
        result = slurper.parseText(manifestJson)
        return result
    }

    public def getLabelsForTag = { def tag ->
        HashMap<String,String> result = new HashMap<String,String>()
        def manifest = getManifest(tag)
        def v1 = null
        def history = manifest.history
        if (history) {
            v1 = history[0]?.v1Compatibility
        }
        if (v1) {
            result = slurper.parseText(v1).config.Labels
        }
        else {
            println '[Warning] Found listing for tag, "' + tag + '" in registry but no manifest. Skipping...'
            manifest?.errors.each { println "Response from registry: $it.message" }
        }
        return result
    }

    public def getIdForTag = { def tag ->
        def result
        result = null
        def manifest = getManifest(tag)
        def v1 = null
        def history = manifest.history
        if (history) {
            v1 = history[0]?.v1Compatibility
        }
        if (v1) {
            result = slurper.parseText(v1).id
        } else {
            println '[Warning] Found listing for tag, "' + tag + '" in registry but no manifest. Skipping...'
            manifest?.errors.each { println "Response from registry: $it.message" }
        }

        return result
    }

    public def getSignatureForTag = { def tag ->
        def result
        def manifest = getManifest(tag)
        result = manifest.signatures[0]?.signature
        return result
    }

    def requestWithAuth = { uri ->
        def result
        HttpGet initialRequest = new HttpGet(uri)

        HttpResponse initialResponse = client.execute(initialRequest)
        def status = initialResponse.getStatusLine().getStatusCode()
        if (status == 401) {
            def token = getToken(initialResponse.getFirstHeader("Www-Authenticate"))
            HttpGet authenticatedRequest = new HttpGet(uri)
            authenticatedRequest.setHeader("Authorization", "Bearer $token")
            HttpResponse authenticatedResponse = client.execute(authenticatedRequest)
            result = authenticatedResponse
        }
        else {
            result = initialResponse
        }

        return result
    }
}
