/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2016. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
import com.urbancode.air.AirPluginTool
import com.urbancode.air.XTrustProvider
import com.urbancode.air.plugin.docker.DockerUtils
import com.urbancode.ud.client.ApplicationClient
import com.urbancode.ud.client.ComponentClient
import com.urbancode.ud.client.EnvironmentClient
import com.urbancode.ud.client.ResourceClient
import com.urbancode.ud.client.VersionClient
import com.urbancode.ud.client.PropertyClient
import groovy.json.JsonSlurper
import java.net.URI
import org.codehaus.jettison.json.JSONArray
import org.codehaus.jettison.json.JSONException
import org.codehaus.jettison.json.JSONObject
import org.yaml.snakeyaml.Yaml

XTrustProvider.install()
int MAX_RETRIES = 3
def COMPONENT_TAG="compose.service"
def airTool = new AirPluginTool(this.args[0], this.args[1])
def props = airTool.getStepProperties()

def serverUri                       = new URI(props["serverUrl"])
String username                     = props["username"] ?: airTool.getAuthTokenUsername()
String password                     = props["password"] ?: airTool.getAuthToken()
String resourceId                   = props["resource"]
String applicationId                = props["application"]
String applicationProcessRequestId  = props["applicationProcessRequest"]
String componentProcessRequestId    = props["componentProcessRequest"]
String environmentId                = props["environment"]
String composeComponentId           = props["component"]
String composeFilesRaw              = props["composeFiles"]
String sourceConfigType             = props["sourceConfigType"]
String componentTemplate            = props["componentTemplate"]

ApplicationClient applicationClient = new ApplicationClient(serverUri, username, password)
ComponentClient componentClient     = new ComponentClient(serverUri, username, password)
EnvironmentClient environmentClient = new EnvironmentClient(serverUri, username, password)
ResourceClient resourceClient       = new ResourceClient(serverUri,  username, password)
VersionClient versionClient         = new VersionClient(serverUri, username, password)
PropertyClient propertyClient       = new PropertyClient(serverUri, username, password)

def composeFilePaths = composeFilesRaw.split("\n")*.trim()
def envPropReferences = [] as Set


if (!composeFilePaths.size()) {
    composeFilePaths << "docker-compose.yml"
}

Yaml yaml = new Yaml()

def slurper = new JsonSlurper()
def unJettison = { json ->
    def result = null
    def text = json.toString()

    if (text) {
        result = slurper.parseText(text)
    }
    return result
}

def applicationName = unJettison(applicationClient.getApplication(applicationId)).name

String deploymentRequestId = componentClient.getComponentProcessRequest(componentProcessRequestId)?.deploymentRequestId

def createComponent = { name, sourceProps, componentProps ->
    println "Creating component: $name"
    def result
    def image = sourceProps["DockerTagImport/dockerImageName"]
    try {
        result = unJettison(componentClient.getComponent(name)).id
    }
    catch (IOException e){

        result = componentClient.createComponent(
                name,
                "Compose service for image: $image",
                sourceConfigType,
                "FULL",
                componentTemplate,
                -1,
                true,
                false,
                sourceProps)

    }

    componentProps.each { componentProp ->
        componentClient.setComponentProperty(
            result.toString(),
            componentProp.key,
            componentProp.value,
            false)
    }

    return result
}

def composeFiles = []
composeFilePaths.each { path ->
    def composeFile = new File(path)
    if (!composeFile.exists()) {
        throw new RuntimeException("Could not find compose file, $path")
    }
    composeFiles << composeFile
    envPropReferences.addAll(DockerUtils.getEnvReferences(composeFile.text))
}

JSONArray propDefs = new JSONArray()
if (envPropReferences.size()) {
    println "Creating environment property definitions for the following properties:"
}
String propSheetDefPath = "components/$composeComponentId/environmentPropSheetDef.-1"
envPropReferences.each { propReference ->
    println "\t$propReference"
    JSONObject propDef = new JSONObject()
    propDef.put("name", propReference)
    propDef.put("label", propReference)
    propDef.put("type", "TEXT")
    propDefs.put(propDef)
}

propertyClient.updatePropDefs(propSheetDefPath, propDefs, false)

def baseComposeFile = composeFiles[0]
println "Loading $baseComposeFile"
def composeYml = yaml.load(baseComposeFile.text)

def serviceList
def composeFileFormat

try {
    composeFileFormat = composeYml.version
    if ("2".equals(composeFileFormat)) {
        println "Found compose file format version $composeFileFormat"
        serviceList = composeYml.services
    }
}
catch (MissingPropertyException e) {
    println "Found compose file format version 1"
    serviceList = composeYml
}

def manifest = [:]
def overrides = [:]

def userSelectedVersions = unJettison(applicationClient.getApplicationProcessRequestVersions(applicationProcessRequestId))
userSelectedVersions.each{ version ->
    def versionProps = versionClient.getVersionProperties(version.name, version.component.name)
    if (versionProps.containsKey("dockerImageTag")) {
        manifest.put(version.component.name, [version.name]) //uDeployRestClient format
        Map <String, String> componentProps = componentClient.getComponentProperties(version.component.name)
        def imageName = componentProps.get("docker.image.name")
        def registry = componentProps.get("docker.registry.name")
        def serviceName = componentProps.get("compose.service")
        def tag = versionProps.get("dockerImageTag")

        if (imageName) {
            String imageSpec = DockerUtils.buildImageSpec(registry, imageName, tag)
            overrides.put(serviceName, ["image" : imageSpec]) //Docker compose format
        }
        else {
            println "Skipping override for $version.component.name"
        }

    }
}

def existingResources = unJettison(resourceClient.getResourceChildren(resourceId))
def applicationComponents = unJettison(applicationClient.getApplicationComponents(applicationId))

serviceList.each{ service ->
    //create component for each service
    if (service.value.image) {
        String componentName = applicationName.replace(" ", "")+"-"+service.key

        def tagSpec = DockerUtils.parseImageSpec(service.value.image)

        def registry = tagSpec.registry
        def image = tagSpec.image
        def tag = tagSpec.version ?: "latest"

        def sourceProps = [:]
        sourceProps.put("DockerTagImport/dockerRegistryName", registry ?: "")
        sourceProps.put("DockerTagImport/dockerImageName", image)

        def componentProps = new Properties()
        componentProps.setProperty("docker.image.name", image)
        componentProps.setProperty("docker.registry.name", registry ?: "")
        componentProps.setProperty("compose.service", service.key)

        String componentId = createComponent(componentName, sourceProps, componentProps).toString()

        //map components to this base resource
        //Currently assigns name componentName - if creating multiple instances of a container, should be unique

        if (!existingResources?.find{ componentName.equals(it.name) }) {
            println "Mapping compose service, $componentName"
            println(existingResources)
            resourceClient.createResource(componentName, null, null, resourceId, componentName)
        }

        if (applicationComponents && !applicationComponents.find{ resourceId.equals(it.id) }) {
            applicationClient.addComponentToApplication(applicationId, componentId)
        }

        componentClient.addTagToComponent(componentId, COMPONENT_TAG)

        def existingVersions = unJettison(componentClient.getComponentVersionsJsonArray(componentName, false))


        def versionId = null

        existingVersions.each { def existingVersion ->
            if (tag.equals(existingVersion.name)) {
                versionId = existingVersion.id
            }
        }

        if (!versionId) {
            versionId = versionClient.createVersion(componentId, tag, "Compose service for image: $image")
        }

        versionClient.setVersionProperty(tag, componentId, "dockerImageTag", tag, false)
        versionClient.setVersionProperty(tag, componentId, "registry", registry ?: "", false)

        if (!manifest.containsKey(componentName)){
            manifest.put(componentName, [tag])
            environmentClient.createDesiredInventoryEntry(deploymentRequestId, environmentId, componentId, versionId.toString(), "Active")
        }
    }
    else {
        println "Skipping component creation for $service.key due to build specification"
    }
}

if (userSelectedVersions) {
    println "Found the following user selected versions:\n$overrides"
    def servicesYaml
    if ("1".equals(composeFileFormat)) {
        servicesYaml = yaml.dump(overrides)
    }
    else {
        servicesYaml = yaml.dump(["version": "2", "services": overrides])
    }

    def composeOverrideFile = new File("ucd-version-overrides.yml")
    if (composeOverrideFile.exists()) {
        composeOverrideFile.delete()
    }
    composeOverrideFile << servicesYaml
}
else {
    println "Did not find any versions selected in UCD"
}

println ("Setting desired inventory to the following versions:")
overrides.each { println "\t[$it.key:\t\t$it.value]" }
