/*
* 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 java.io.File
import org.yaml.snakeyaml.Yaml

import com.urbancode.air.XTrustProvider
import com.urbancode.air.plugin.docker.DockerUtils
import com.urbancode.air.AirPluginTool
import com.urbancode.air.CommandHelper
import com.urbancode.ud.client.ResourceClient
import com.urbancode.ud.client.ComponentClient
import com.urbancode.ud.client.ApplicationClient
import com.urbancode.ud.client.EnvironmentClient

XTrustProvider.install()
int MAX_RETRIES = 3
def COMPONENT_TAG='compose.service'

// Get step properties
def airTool = new AirPluginTool(this.args[0], this.args[1])
def props = airTool.getStepProperties()
def workdir = new File('.')
String serverUrl                 = props['serverUrl']
String username                  = props['username']
String password                  = props['password']
String resourceId                = props['resource']
String applicationId             = props['application']
String componentProcessRequestId = props['componentProcessRequest']
String composeProjectName        = props['composeProjectName']
String composeOptions            = props['composeOptions']
String stackName                 = props['stackName']?.trim()
String environmentId             = props['environment']
String propertyPrefix            = props['propertyPrefix']
String scriptPathsRaw            = props['scriptPaths']
String composeFilesRaw           = props['composeFiles']
String envPropValues             = props['envPropValues']
String commandPath               = props['commandPath']?:"docker-compose"
Boolean saveScript               = Boolean.valueOf(props['saveScript'])

// Sanitize properties
def serverUri = DockerUtils.stringToUri(serverUrl)
if (serverUri.equals(new URI(''))) {
    System.exit(1)
}
if (!username) {
    username = airTool.getAuthTokenUsername()
}
if (!password) {
    password = airTool.getAuthToken()
}

def scriptPaths = DockerUtils.toTrimmedList(scriptPathsRaw, ',')
def composeFilePaths = DockerUtils.toTrimmedList(composeFilesRaw, '\n')
if (!composeFilePaths) {
    composeFilePaths << 'docker-compose.yml'
}

// Get compose file version
def baseComposeFile = composeFilePaths[0]
println "Loading ${baseComposeFile}"
def composeFile = new File(baseComposeFile)
if (!composeFile.exists()) {
    throw new RuntimeException("Could not find compose file, ${baseComposeFile}")
}
Yaml yaml = new Yaml()
def composeYml = yaml.load(composeFile.text)
def composeFileFormat
try {
    composeFileFormat = composeYml.version
}
catch (MissingPropertyException e) {
    composeFileFormat = "1"
}
println "Found compose file format version ${composeFileFormat}"

def versionOverridesFile = new File('ucd-version-overrides.yml')
if (versionOverridesFile.exists() && versionOverridesFile.text) {
    composeFilePaths << versionOverridesFile.path
}

def ch = new CommandHelper(workdir)

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

String deploymentRequestId = componentClient.getComponentProcessRequest(componentProcessRequestId)?.deploymentRequestId
def components = DockerUtils.parseTextAsJson(applicationClient.getApplicationComponents(applicationId))

def desiredVersionMap = [:]
def services = [:]
def componentMap = [:]

// Get services
components.each { component ->
    componentMap.put(component.name, component)
    def desiredInventoryEntry = DockerUtils.parseTextAsJson(environmentClient.getLatestEnvironmentInventoryByComponent(environmentId, applicationId, component.name.replace(' ', '%20')))
    if (desiredInventoryEntry) {
        desiredVersionMap.put(component.name, desiredInventoryEntry.version.name)
    }
    if (component.tags.find{ COMPONENT_TAG.equals(it.name) }) {
        def componentProps = componentClient.getComponentProperties(component.name)
        component.image = componentProps['docker.image.name']

        def version = desiredVersionMap.get(component.name) //Needs to be replaced with property for service name
        String imageTag = "${component.image}:${version}"
        services.put(component.name, ['image': imageTag])
    }
}

// Environment properties
def envVars = [:]
if(envPropValues) {
    List tempVals = DockerUtils.toTrimmedList(envPropValues,'\n|,')
    tempVals.each{ it ->
        String[] parts = it.split("=", 2)*.trim()
        def propName = parts[0]
        def propValue = parts.size() == 2 ? parts[1] : ''
        envVars.put(propName, propValue)
    }
}

// command helper arguments
def args = []
File script
final def isWindows = System.getProperty('os.name').contains('Windows')
if (isWindows) {
    script = File.createTempFile("script", ".bat", workdir)

    // Write the Environment Variables
    envVars.each { var ->
        script << "set ${var.getKey()}=${var.getValue()}\n"
    }

    args << 'cmd'
    args << '/C'
}
else {
    script = File.createTempFile("script", ".sh", workdir)

    // Environment files
    scriptPaths.each { filePath ->
        def envScript = new File(filePath)
        if (envScript.exists()) {
            envScript.setExecutable(true)
            script << "source \"${filePath}\"\n"
        }
        else {
            throw new RuntimeException("Could not find env file at ${filePath}")
        }
    }

    // Write the Environment Variables
    envVars.each { var ->
        script << "export ${var.getKey()}=\"${var.getValue()}\"\n"
    }
}
if (!saveScript) {
    script.deleteOnExit()
}
script.setExecutable(true)

args << script.getCanonicalPath()

/*
 * Depending on compose file version, build the 'docker-compose up' or 'docker stack deploy' command.
 * If compose file version is 3+, and there are multiple compose files, then we will merge the compose files
 * and any overrides file using 'docker-compose config' before invoking the 'docker stack deploy' command.
 * When/if multiple yaml files are supported by 'docker stack' cmds, then we can do away with the
 * 'docker-compose config' command.
 */
def composeFileVersion = composeFileFormat.isInteger() ? composeFileFormat.toInteger() : null
def cmdMsg, cmdArgs
if (composeFileVersion >= 3) {
    cmdMsg = "Executing Stack Deploy..."

    // Build the 'docker-compose config' cmd
    def configOutYAML = 'configOut.yaml'
    cmdArgs = [DockerUtils.findDockerComposeExecutable(commandPath)]
    if (composeOptions) {
        cmdArgs << composeOptions
    }
    if (composeProjectName) {
        cmdArgs << '-p'
        cmdArgs << composeProjectName
    }
    composeFilePaths.each { path ->
        cmdArgs << '-f'
        cmdArgs << path
    }
    cmdArgs << 'config'
    cmdArgs << '>'
    cmdArgs << configOutYAML
    cmdArgs << '2>'
    cmdArgs << 'configErr.txt'

    script << cmdArgs.join(' ')
    script << System.getProperty("line.separator")

    // Build the 'docker stack deploy' cmd
    cmdArgs = ["docker", "stack", "deploy"]
    cmdArgs << '-c'
    cmdArgs << configOutYAML
    if (stackName) {
        cmdArgs << stackName
    }
    else {
        throw new RuntimeException("A stack name must be specified when compose file version is >= 3.")
    }
    script << cmdArgs.join(' ')
}
else {
    // compose up arguments
    cmdMsg = "Executing Compose Up..."
    cmdArgs = [DockerUtils.findDockerComposeExecutable(commandPath)]
    if (composeOptions) {
        cmdArgs << composeOptions
    }
    if (composeProjectName) {
        cmdArgs << '-p'
        cmdArgs << composeProjectName
    }
    composeFilePaths.each { path ->
        cmdArgs << '-f'
        cmdArgs << path
    }
    cmdArgs << 'up'
    cmdArgs << '-d'
    script << cmdArgs.join(' ')
}

// Print Script Contents
println "============ Script Contents ============"
println script.getText()
println "========================================="

// Run cmd (docker-compose up or docker stack deploy)
ch.runCommand(cmdMsg, args)

//Update Actual (Resource) Inventory
def children = DockerUtils.parseTextAsJson(resourceClient.getResourceChildren(resourceId))
children.each { childResource ->
    def roles = DockerUtils.parseTextAsJson(resourceClient.getResourceRoles(childResource.id))
    roles.each { role ->
        //if child has role, update inventory
        def componentName = role.name
        def versions = DockerUtils.parseTextAsJson(componentClient.getComponentVersionsJsonArray(componentName, false))
        def versionId
        def resourceInventoryEntry = services.get(componentName)
        if (resourceInventoryEntry) {
            def versionName = desiredVersionMap.get(componentName)
            println "Updating inventory for ${componentName} - ${versionName}"
            versions.each {
                if (versionName.equals(it.name)) {
                    println "\t${it.name}"
                    versionId = it.id
                }
            }
            resourceClient.createResourceInventoryEntry(deploymentRequestId, childResource.id, componentMap.get(componentName).id, versionId, 'Active')
        }
    }
}
