/*
* Licensed Materials - Property of IBM* and/or HCL**
* UrbanCode Deploy
* UrbanCode Build
* UrbanCode Release
* AnthillPro
* (c) Copyright IBM Corporation 2011, 2017. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*
* * Trademark of International Business Machines
* ** Trademark of HCL Technologies Limited
*/
package com.urbancode.air.plugin.udeploy.environments

import com.urbancode.air.AirPluginTool
import com.urbancode.ud.client.EnvironmentClient
import java.util.UUID
import java.net.URI
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;


public class EnvironmentHelper {
    def apTool
    def props = []
    def udUser
    def udPass
    def weburl
    EnvironmentClient client

    public EnvironmentHelper(def apToolIn) {
        apTool = apToolIn
        props = apTool.getStepProperties()
        udUser = apTool.getAuthTokenUsername()
        udPass = apTool.getAuthToken()
        weburl = System.getenv("AH_WEB_URL")
        client = new EnvironmentClient(new URI(weburl), udUser, udPass)

        com.urbancode.air.XTrustProvider.install()
    }

    public def createEnvironment() {
        def appName = props['application']
        def envName = props['environment']
        def description = props['description']
        def requireApprovals = Boolean.valueOf(props['requireApprovals'])
        def appendSuffix = Boolean.valueOf(props['suffix'])
        def propertiesString = props['properties']
        def nodePropertiesString = props['nodeProperties']
        def nodePropertiesJSON = null

        def blueprintName = props['blueprint']
        def baseResource = props['baseResource']
        def envProfileName = props['envProfileName']

        if (!appName) {
            throw new IllegalArgumentException("No application was specified")
        }
        if (!envName) {
            throw new IllegalArgumentException("No environment was specified")
        }
        if (!description) {
            description = ""
        }

        if (blueprintName && !baseResource) {
            throw new IllegalArgumentException("A base resource path must be specified when using "+
                    "a blueprint to create an environment.");
        }

        if (appendSuffix) {
            envName = envName + "-" + UUID.randomUUID().toString().substring(0,8)
        }

        if (nodePropertiesString) {
            nodePropertiesJSON = new JSONObject();
            String[] splitNewlines = nodePropertiesString.split("\n");
            def properties = new Properties()
            properties.load(new ByteArrayInputStream(nodePropertiesString.getBytes()))

            for (String line : splitNewlines) {
                String[] splitEquals = line.split("=", 2);
                String key = splitEquals[0];
                String value = splitEquals[1];
                int lastSlash = key.lastIndexOf('/')
                String node = key.substring(0, lastSlash)
                String name = key.substring(lastSlash+1)
                JSONObject nodeJSON = nodePropertiesJSON.optJSONObject(node) ?: new JSONObject();
                nodeJSON.put(key, value)
                nodePropertiesJSON.put(node, nodeJSON)
            }
        }

        UUID envUUID = client.createEnvironment(appName, envName, description, "white",
        requireApprovals, blueprintName, baseResource, envProfileName, nodePropertiesJSON)
        println "Created environment with ID " + envUUID.toString() + "\n"

        if (propertiesString) {
            def properties = new Properties()
            properties.load(new ByteArrayInputStream(propertiesString.getBytes()))

            for (def key : properties.keySet()) {
                def value = properties.get(key)

                client.setEnvironmentProperty(envName, appName, key, value, false)
                println "Environment property " + key + " was set for environment " + envName
            }
        }

        apTool.setOutputProperty("environment.name", envName)
        apTool.setOutputProperty("environment.id", envUUID.toString())
        apTool.setOutputProperties()
    }

    public def createEnvironmentFromTemplate() {
    String appId   = props['application'].trim()
        String envName = props['environment'].trim()
        String description  = props['description'].trim()
        String templateId   = props['templateId'].trim()
        String templateName = props['templateName'].trim()

        if (!appId) {
            throw new RuntimeException("[Error] The Application ID must be specified.")
        }
        if (!envName) {
            throw new RuntimeException("[Error] The Environment Name must be specified.")
        }
        if (!templateId && !templateName) {
            throw new RuntimeException("[Error] Either the Enviroment Template Id or Name property must be specified.")
        }

        JSONObject envResponse = client.createEnvironmentFromTemplate(appId, envName, description, templateId, templateName)
        println "Successfully created an environment from a template."
        println "Response: " + envResponse.toString() + "\n"


        apTool.setOutputProperty("environment.name", envResponse.getString("name"))
        apTool.setOutputProperty("environment.id", envResponse.getString("id"))
        apTool.setOutputProperty("environment.templateId", envResponse.getString("templateId"))
        apTool.setOutputProperties()

    }

    public def createMultipleEnvironments() {
        String json = props['json']
        JSONArray envsJson
        File jsonFile = new File(json)

        try {
            if (jsonFile.exists() && jsonFile.isFile()) {
                envsJson = new JSONArray(jsonFile.getText('UTF-8'))
            }
            else {
                envsJson = new JSONArray(json)
            }
        }
        catch (JSONException ex) {
            println("[Error] A syntax error occurred while parsing the JSON object.")
            throw ex
        }

        def envUUIDs = []

        for (int i = 0; i < envsJson.length(); i++) {
            UUID result
            JSONObject envJson = envsJson.get(i)
            String appName, name, description, color, blueprintName, baseResource, envProfileName, templateName, templateId
            boolean requireApprovals
            JSONObject nodeProperties, properties

            if (envJson.has("application")) {
                appName = envJson.getString("application")
            }
            else {
                throw new IllegalArgumentException("A required field 'application' was not found in the following JSON object:\n"
                    + envJson)
            }
            if (envJson.has("name")) {
                name = envJson.getString("name")
            }
            else {
                throw new IllegalArgumentException("A required field 'name' was not found in the following JSON object:\n"
                    + envJson)
            }
            if (envJson.has("description")) {
                description = envJson.getString("description")
            }
            if (envJson.has("requireApprovals")) {
                requireApprovals = envJson.getBoolean("requireApprovals")
            }
            if (envJson.has("blueprint")) {
                blueprintName = envJson.getString("blueprint")
            }
            if (envJson.has("baseResource")) {
                templateName = envJson.get("baseResource")
            }
            if (envJson.has("envProfileName")) {
                envProfileName = envJson.getBoolean("envProfileName")
            }
            if (envJson.has("nodeProperties")) {
                nodeProperties = envJson.get("nodeProperties")
            }
            if (envJson.has("templateId")) {
                templateId = envJson.getInt("templateId")
            }
            if (envJson.has("templateName")) {
                templateName = envJson.getInt("templateName")
            }

            println("[Action] Creating new environment '${name}'...")

            if (templateName || templateId) {
                result = client.createEnvironmentFromTemplate(appName, name, description, templateId, templateName)
            }
            else {
                result = client.createEnvironment(appName, name, description, "white", requireApprovals,
                    blueprintName, baseResource, envProfileName, nodeProperties)
            }

            if (!result) {
                throw new RuntimeException("Failed to create new environment using JSON:\n${envJson}")
            }

            println("[Ok] Successfully created environment.")
            envUUIDs << result

            if (envJson.has("properties")) {
                JSONObject propsJson = envJson.get("properties")

                for (def propKey : propsJson.keys()) {
                    try {
                        client.setEnvironmentProperty(name, appName, propKey, propsJson.getString(propKey), false)
                    }
                    catch (IOException ex) {
                        println("[Error] Failed to set property '${propKey}' on environment '${name}'.")
                        throw ex
                    }
                }
            }

        }

        apTool.setOutputProperty("environment.ids", envUUIDs.join(','))
        apTool.storeOutputProperties()
    }

    public def deleteEnvironment() {
        def appName = props['application']
        def envName = props['environment']

        client.deleteEnvironment(appName, envName)
        println "Deleted Environment "+envName;
    }

    public def setEnvironmentProperty() {
        def appName = props['application']
        def envName = props['environment']
        def propName = props['name']
        def propValue = props['value']
        def isSecure = Boolean.valueOf(props['isSecure'])

        if (!propName) {
            throw new IllegalArgumentException("no property name was specified")
        }
        if (!propValue) {
            propValue = ""
        }
        if (!appName) {
            appName = ""
        }
        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        client.setEnvironmentProperty(envName, appName, propName, propValue, isSecure)
        println "Environment property " + propName + " was set for environment " + envName
    }

    def setComponentEnvironmentProperty() {
        def compName = props['component']
        def appName = props['application']
        def envName = props['environment']
        def propName = props['name']
        def propValue = props['value']
        def isSecure = Boolean.valueOf(props['isSecure'])

        if (!propName) {
            throw new IllegalArgumentException("no property name was specified")
        }
        if (!propValue) {
            propValue = ""
        }
        if (!appName) {
            appName = ""
        }
        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        if (!compName) {
            throw new IllegalArgumentException("no component was specified")
        }
        client.setComponentEnvironmentProperty(compName, envName, appName, propName, propValue, isSecure)
        println "component environment property " + propName + " was set for environment " + envName
    }

    def getEnvironmentProperties() {
        def appName = props['application']
        def envName = props['environment']

        if (!appName) {
            appName = ""
        }
        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }

        Map<String, String> propMap = client.getEnvironmentProperties(envName, appName)
        Set<String> keySet = propMap.keySet()
        for(String key : keySet) {
            apTool.setOutputProperty(key, propMap.get(key))
        }
        apTool.setOutputProperties()
    }

    def getComponentEnvironmentProperties() {
        def appName = props['application']
        def envName = props['environment']
        def compName = props['component']

        if (!appName) {
            appName = ""
        }
        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        if (!compName) {
            throw new IllegalArgumentException("no component was specified")
        }

        Map<String, String> propMap = client.getComponentEnvironmentProperties(compName, envName, appName)
        Set<String> keySet = propMap.keySet()
        for(String key : keySet) {
            apTool.setOutputProperty(key, propMap.get(key))
        }
        apTool.setOutputProperties()
    }

    def verifyInventoryStatus() {
        def envName = props['environment']
        def appName = props['application']
        def component = props['component']
        def version = props['version']
        def inventoryStatus = props['inventoryStatus']

        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        if (!appName) {
            appName = ""
        }
        if (!component) {
            throw new IllegalArgumentException("no component was specified")
        }
        if (!version) {
            throw new IllegalArgumentException("no version was specified")
        }
        if (!inventoryStatus) {
            throw new IllegalArgumentException("no status was specified")
        }

        boolean verified = client.verifyInventoryStatus(envName, appName, component, version, inventoryStatus)
        if (verified) {
            println "Confirmed that environment ${envName} has component ${component} in inventory with version ${version} and status ${inventoryStatus}"
            apTool.setOutputProperty("verified", "true")
        }
        else {
            println "Environment ${envName} does NOT have component ${component} in inventory with version ${version} and status ${inventoryStatus}"
            apTool.setOutputProperty("verified", "false")
        }
        apTool.setOutputProperties()
    }

    def environmentExists() {
        def envName = props['environment']
        def appName = props['application']

        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        if (!appName) {
            appName = ""
        }

        try {
            def envID = client.getEnvironmentUUID(envName, appName)
            println "Environment with name ${envName} was found."
            apTool.setOutputProperty("exists", "true");
        }
        catch(IOException e) {
            if(e.getMessage().contains("404") || e.getMessage().contains("400")) {
                println "Request was successful but no environment with name ${envName} was found."
                apTool.setOutputProperty("exists", "false");
            }
            else {
                println "An error occurred during your request."
                throw new IOException(e);
            }
        }
        apTool.setOutputProperties()
    }

    def getLatestEnvironmentInventoryByComponent() {
        def envName  = props['environment']
        def appName  = props['application']
        def compName = props['component']

        if (!envName) {
            throw new IllegalArgumentException("No environment was specified")
        }
        if (!appName) {
            throw new IllegalArgumentException("No application was specified")
        }
        if (!compName) {
            throw new IllegalArgumentException("No component was specified")
        }
        JSONObject inventory = client.getLatestEnvironmentInventoryByComponent(
            envName, appName, compName);

        if (inventory.length() == 0) {
            println "No version for that component exists for that environment"
        }
        else {
            def verName = inventory.get("version").getAt("name")
            def verId = inventory.get("version").getAt("id")
            apTool.setOutputProperty("version.name", verName)
            apTool.setOutputProperty("version.id", verId)
            apTool.setOutputProperties()
        }
    }

    public def addEnvironmentToTeam() {
        def appName = props['application']
        def envNames = (props['environment'])?.split(',')*.trim()
        def teamName = props['team']
        def typeName = props['type']

        if (envNames.size() == 0) {
            throw new IllegalArgumentException("no environments were specified")
        }
        if (!teamName) {
            throw new IllegalArgumentException("no team was specified")
        }

        for (def envName : envNames) {
            client.addEnvironmentToTeam(appName, envName, teamName, typeName)
            println "Environment '${envName}' was added to team for the given type classification."
        }

    }

    public def addBaseResource() {
        def appName = props['application']
        def envName = props['environment']
        def resName = props['resource']

        if (!envName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        if (!resName) {
            throw new IllegalArgumentException("no resource was specified")
        }

        client.addEnvironmentBaseResource(appName, envName, resName)
        println "The base resource was added to the environment."
    }

    public def getEnvironmentInfo() {
        def environmentName = props['environment']
        if (!environmentName) {
            throw new IllegalArgumentException("no environment was specified")
        }
        def applicationName = props['application']

        def environmentJson = client.getEnvironment(environmentName, applicationName)
        def environmentInfoMap = client.getJSONAsProperties(environmentJson)
        for (String key : environmentInfoMap.keySet()) {
            apTool.setOutputProperty(key, environmentInfoMap.get(key))
        }
        apTool.setOutputProperties()

        println environmentJson
    }
}
