package com.urbancode.air.plugin.docker

import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import org.apache.http.impl.client.DefaultHttpClient
import com.urbancode.air.AirPluginTool
import com.urbancode.air.XTrustProvider
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.client.methods.HttpGet
import java.util.regex.Matcher
import java.util.regex.Pattern
import org.apache.http.client.utils.URIBuilder
import java.net.URI
import groovy.json.JsonSlurper

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 isICS
    def bearerToken
    def spaceGuid

    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.isICS = props['isICS'].toBoolean()

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

        this.slurper = new JsonSlurper()

        HttpClientBuilder builder = new HttpClientBuilder()
        if (username && (!isICS)) {
            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()
    }

    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 loadCFAuthInfo() {
        def jsonFile = new File (System.getProperty("user.home") + "/.cf/config.json")
        def configInfo = slurper.parseText(jsonFile.text)
        bearerToken = configInfo.AccessToken
        if(bearerToken.toLowerCase().startsWith("bearer ")) {
            bearerToken = bearerToken.substring(7)
        }
        spaceGuid = configInfo.SpaceFields.Guid
    }

    def getBaseUri() {
        def protocol = "https://"
        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 getICSImageUri = { tag ->
        return new URIBuilder(getBaseUri()).setPath("/v3/images/${imageName}:${tag}/json").build()
    }

    def getImageListUri() {
        return new URIBuilder(getBaseUri()).setPath("/v3/images/json").build()
    }

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

    void pingRegistry() {
        def response = requestWithAuth(getPingUri())
        def status = response.statusLine.statusCode
        if (status != 200) {
            println "Response from registry server:"
            println response.statusLine
            println response.entity?.content?.text
            throw new RuntimeException("Unable to ping registry. Please check credentials and make sure that registry supports v2 api.")
        }

    }

    public def getTags() {
        String[] tags = ""
        def tagsJson
        if (isICS) {
            loadCFAuthInfo()
        }
        else {
            println "Connecting to registry..."
            pingRegistry()
            println "...Registry ping successful."
            println ""
        }
        println "Requesting list of tags for ${imageName}..."
        if (isICS) {
            tagsJson = requestWithAuth(getImageListUri()).entity?.content.text
            def images = slurper.parseText(tagsJson).Image
            images.each { image ->
                def split = image.split(':')
                if (split[0].equals(imageName)) {
                    tags += split[1]
                }
            }
        }
        else {
            tagsJson = requestWithAuth(getTagsUri()).entity?.content?.text
            tags = slurper.parseText(tagsJson).tags
        }

        if (tags) {
            println "...Successfully retrieved ${tags.size} tags."
            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 getIdForTag = { def tag ->
        def result
        if (isICS) {
            def idJson = requestWithAuth(getICSImageUri(tag)).entity?.content?.text
            result = slurper.parseText(idJson).Id
        }
        else {
            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)

        if (isICS) {
            initialRequest.addHeader("Content-Type", "application/json")
            initialRequest.addHeader("X-Auth-Token", bearerToken)
            initialRequest.addHeader("X-Auth-Project-Id", spaceGuid)
        }

        HttpResponse initialResponse = client.execute(initialRequest)
        def status = initialResponse.getStatusLine().getStatusCode()

        if ((status == 401) && (!isICS)) {
            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 if ((status==401) && (isICS)) {
            println "Authentication error. Login to CF CLI."
            System.exit(1)
        }
        else {
            result = initialResponse
        }

        return result
    }
}
