/**
 * 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 org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder

import com.urbancode.air.CommandHelper
import com.urbancode.air.ExitCodeException
import com.urbancode.air.XTrustProvider
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

public class ICSClient {
    CommandHelper helper
    final def workDir = new File('.').canonicalFile
    def registry
    def username
    def password
    def proxyHost
    def proxyPortString
    def proxyUsername
    def proxyPassword
    def slurper
    def client
    def imageName
    def allowInsecure
    def api
    def bearerToken
    def spaceGuid
    def protocol
    def space
    def org
    def cfHomeDir

    public ICSClient( def props) {
        this.registry = props['dockerRegistryName']
        this.username = props['dockerRegistryUsername']
        this.password = props['dockerRegistryPassword']
        this.imageName = props['dockerImageName']
        this.allowInsecure = (props['allowInsecure']?:"false").toBoolean()
        this.api = props['api']?.trim()
        this.space = props['space']
        this.org = props['org']
        this.cfHomeDir = props['CF_HOME'] // get location of config.json file

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

        this.protocol = "https://"

        this.slurper = new JsonSlurper()

        helper = new CommandHelper(workDir)

        HttpClientBuilder builder = new HttpClientBuilder()

        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] Loading Cloud Foundry authentication..."
        cfLogin()
        loadCFAuthInfo()
        println "...Authentication successfully loaded."
        println ""
    }

    // set the api for the cf executable and log in
    void cfLogin() {
        def commandArgs

        // Set cf api
        if (api) {
            commandArgs = ["cf", "api", api]
            try {
                runHelperCommand("[Action] Setting cf target api", commandArgs)
            }
            catch (ExitCodeException e) {
                println "[Error] An error occurred when setting the api to ${api}"
                println "[Error] ICS API is not set correctly"
                throw e
            }
        }

        // Log in
        commandArgs = [
                "cf",
                "login",
                "-u",
                username,
                "-p",
                password
        ]
        if (space) {
            commandArgs << "-s"
            commandArgs << space
        }
        if (org) {
            commandArgs << "-o"
            commandArgs << org
        }

        try {
            runHelperCommand("[Action] Logging into CloudFoundry", commandArgs)
        }
        catch(ExitCodeException e) {
            println "[Error] Authentication error. Check username, password, org, and space credentials."
            throw e
        }

        // IC Log in
        commandArgs = [
                "cf",
                "ic",
                "login"
        ]

        try {
            runHelperCommand("[Action] Logging into IBM Containers", commandArgs)
        }
        catch(ExitCodeException e) {
            println "[Error] IBM Containers Login Error. Check username, password, org, and space credentials."
            println "[Possible Solution] Is the 'cf ic' IBM Containers CLI installed? Refer to the plugin documentation for directions."
            throw e
        }
    }

    def runHelperCommand(def message, def command) {
        try {
            helper.runCommand(message, command)
        } catch(ExitCodeException e){
            def errorMessage = "[Error] An error occurred while running the following command: ${command}"
            throw new ExitCodeException(errorMessage, e)
        }
    }

    def loadCFAuthInfo() {

        if (!cfHomeDir && System.getenv("CF_HOME")) {
            cfHomeDir = System.getenv("CF_HOME")
            println "[Ok] CF_HOME environment variable found. Setting to '${cfHomeDir}'."
        }
        else if (!cfHomeDir) {
            cfHomeDir = System.getProperty("user.home") + File.separator + ".cf"
            println "[Ok] CF_HOME variable not found. Defaulting to '${cfHomeDir}'."
        }

        if (!cfHomeDir.endsWith(File.separator)) {
            cfHomeDir += File.separator
        }

        try {
            def jsonFile = new File(cfHomeDir + "config.json")
            def configInfo = slurper.parseText(jsonFile.text)
            bearerToken = configInfo.AccessToken
            spaceGuid = configInfo.SpaceFields.GUID
        }
        catch (FileNotFoundException ex) {
            println "[Error] Cannot find the config.json file: ${ex.getMessage()}"
            println "[Possible Solution] Confirm the CF_HOME variable is specified correctly. Example: 'C:\\\\Users\\\\User\\\\.cf'"
            throw ex
        }
    }

    def getBaseUri() {
        return new URIBuilder("${protocol}${registry}").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()
    }

    public def getTags() {
        def tags = []
        def tagsJson
        List<String> imageNames = new ArrayList<String>()
        println "[Ok] Listing tags for ${imageName}..."

        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]
            }
            if(!imageNames.contains(split[0])) {
                imageNames << split[0]
            }
        }

        println "Image Names Found: " + imageNames.toString()
        if (tags) {
            println "...Successfully retrieved ${tags?.size()} tag(s)."
            println ""
        } else {
            println "No tags found"
        }

        return tags
    }

    private def parseJson = { String tag ->
        Map result
        String json = requestWithAuth(getICSImageUri(tag)).entity?.content?.text
        if (json) {
          result = slurper.parseText(json)
        }
        return result
    }

    public def getLabelsForTag = { String tag ->
        Map<String,String> result = new HashMap<String,String>()
        result = parseJson(tag).Config.Labels
        return result
    }

    public def getIdForTag = { String tag ->
        String result
        result = parseJson(tag).Id
        return result
    }

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

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

        try {
            initialResponse = client.execute(initialRequest)
        }
        catch (ConnectException ex) {
            throw new ConnectException("[Error] Could not connect to the registry: '${registry}'. " +
            "Please verify the registry is reachable and the credentials are valid.")
        }
        catch (UnknownHostException ex) {
            throw new UnknownHostException("[Error] Could not connect to the registry: '${registry}'. " +
            "Please verify the registry is valid.")
        }
        int status = initialResponse.getStatusLine().getStatusCode()

        if (status < 200 || status >= 300) {
            throw new Exception("[Error] Authentication error. Recieved status code: '${status}'\n" +
            "Error Response: ${initialResponse.entity?.content?.text}")
        }
        else {
            result = initialResponse
        }

        return result
    }
}
