/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* (c) Copyright IBM Corporation 2017. 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.kubernetes

import com.urbancode.air.CommandHelper
import com.urbancode.air.ExitCodeException
import java.util.regex.Matcher
import java.util.regex.Pattern
import groovy.json.JsonException
import groovy.json.JsonSlurper

class KubernetesHelper {

    private CommandHelper ch
    private String home

    /**
    *  @param workDir The working directory for the CommandHelper CLI
    *  @param home The full path to the kubectl script location
    */
    public KubernetesHelper(File workDir, String home) {
        if (home) {
            this.home = home
        }
        else {
            this.home = 'kubectl'
        }
        ch = new CommandHelper(workDir)
        ArrayList args = []
        // don't check for kubectl if running a helm command
        if (!home.equalsIgnoreCase("helm")) {
            if (!runCommand('[action]  Checking kubectl home...', args, 'kubectl home set.', 'Could not set kubectl home.')) {
                System.exit(1)
            }
        }
    }

    /**
    *  @param proc The process to retrieve the standard output and standard error from
    *  @return An array containing the standard output and standard error of the process
    */
    public String[] captureCommand(Process proc) {
        StringBuffer sout = new StringBuffer()
        StringBuffer serr = new StringBuffer()
        proc.waitForProcessOutput(sout, serr)
        proc.out.close()
        return [sout.toString(), serr.toString()]
    }

    /**
    *  @param message The message to output before running the command
    *  @param args An ArrayList of arguments to be executed by the command prompt
    *  @param success The message to display if the command completes without errors
    *  @param failure The message to display if the command completes with errors
    *  @return true if the command is run without any Standard Errors, false otherwise
    */
    public boolean runCommand(String message, ArrayList args, String success, String failure) {
        ArrayList commandLine = [home]
        args.each() { arg ->
            commandLine << arg
        }
        boolean status
        try {
            ch.runCommand(message, commandLine) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println ("[error]  ${failure}")
                    println serr
                    status = false
                }
                else {
                    println ("[ok]  ${success}")
                    if (args.size() > 0) {
                        println sout
                    }
                    status = true
                }
            }
        }
        catch (ExitCodeException ece) {
            return false
        }
        return status
    }

    /**
     *  @param message The message to output before running the command
     *  @param args An ArrayList of arguments to be executed by the command prompt
     *  @param success The message to display if the command completes without errors
     *  @param failure The message to display if the command completes with errors
     *  @return the standard output from the command
     */
     public String getOutputFromCommand(String message, ArrayList args, String success, String failure) {
        ArrayList commandLine = [home]
        args.each() { arg ->
            commandLine << arg
        }
        String output = "";
        try {
            ch.runCommand(message, commandLine) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println ("[error]  ${failure}")
                    println serr
                }
                else {
                    println ("[ok]  ${success}")
                    if (args.size() > 0) {
                        println sout
                    }
                    output = sout;
                }
            }
        }
        catch (ExitCodeException ece) {
            return output
        }
        return output
    }

    public void addEnvironmentVariable(String key, String value) {
        ch.addEnvironmentVariable(key, value);
    }

    /**
    *  @param args The list of arguments to add the global configuration options to
    *  @param url A url to specify for the Kubernetes server
    *  @param username A username to specify for authentication to the Kubernetes server
    *  @param password A password to specify for authentication to the kubernetes server
    *  @param namespce A namespce to specify the scope on the Kubernetes server of the CLI request
    *  @param globals A list of any other global arugments
    */
    public void setGlobals(ArrayList args, String url, String username, String password, String namespace, String globals) {
        if (url) {
            args << '--server=' + url
        }
        if (username) {
            args << '--username=' + username
        }
        if (password) {
            args << '--password=' + password
        }
        if (namespace) {
            args << '--namespace=' + namespace
        }
        if (globals) {
            globals.split("[\r\n]+").each() { global ->
                String trimmed = global.trim()
                int ndx = trimmed.indexOf(' ')
                if (ndx < 0) {
                    args << trimmed
                }
                else {
                    args << trimmed.substring(0, ndx)
                    args << trimmed.substring(ndx+1)
                }
            }
        }
    }

    /**
    *  @param args The list of arguments to add the global configuration options to
    *  @param flags The list of flags to add to the args
    */
    public void setFlags(ArrayList args, String flags) {
        if (flags) {
            flags.split("[\r\n]+").each() { flag ->
                args << flag
            }
        }
    }

    /**
     *  @param url The string to attempt to convert to a URI
     *  @return The URI representation of the url, false if any syntax errors
     */
     public static def stringToUri(String url) {
         try {
             return new URI(url)
         }
         catch (URISyntaxException use) {
             println ("[error]  Invalid syntax for URL: ${use.getMessage()}.")
             println ('[possible solution]  Please update the step configuration with a valid URL.')
             return new URI('')
         }
     }

    /**
     *  @param list The list to trim whitespaces and remove null entries
     *  @param delimiter The string that separates each entry
     */
     public static def toTrimmedList(def list, String delimiter) {
         return list.split(delimiter).findAll{ it?.trim() }.collect{ it.trim() }
     }

    /**
     *  @param jsonText The text to turn into a Json object
     *  @return An object representation of a Json structure
     */
     public static def parseTextAsJson(def jsonText) {
         def parsedText
         def text = jsonText.toString()
         if (text) {
             try {
                 parsedText = new JsonSlurper().parseText(text)
             }
             catch (JsonException jse) {
                 println ("[error]  Could not parse text as JSON: ${text.replace('\n', '')}")
             }
         }
         return parsedText
     }

    /**
     *  @param input The string to check for environment references in
     *  @return A set of keys referencing environments
     */
     public static def getEnvReferences(String input) {
         def result = [] as Set
         if (input != null && input.trim().length() > 0) {
             Pattern pattern = Pattern.compile('\\$\\{[^}]*}')
             Matcher matcher = pattern.matcher(input)
             while (matcher.find()) {
                 String key = input.substring(matcher.start() + 2, matcher.end() - 1)
                 result << key
             }
         }
         return result
     }

    /**
     *  @param rawImageSpec The raw, unformatted image spec
     *  @return The specs image, registry and version of the image spec
     */
     public static def parseImageSpec(String rawImageSpec) {
         def result = new Expando()

         // qualified domain         registry.tld/org/namespace/image:1.0
         // -------- or --------     registry.tld/org/namespace/image
         // simple host and port     localhost:5000/namespace/image:1.0
         // -------- or --------     localhost:5000/namespace/image
         // implicit library         registry.tld/namespace/image:1.0
         // ------   or --------     registry.tld/namespace/image
         // no/implicit registry     namespace/image:1.0
         // ------   or --------     namesapce/image
         // implicit local library   image:1.0
         // ------   or --------     image
         // colon in repo spec       my:image:1.0
         // ------   or --------     my:image
         // Repo is everything before first slash, unless spec contains no dots and no colon before first slash.
         // If registry spec exists, remove it and trailing slash - called Repository Spec
         def registryImageArray = rawImageSpec.split("/", 2)

         //Break this string into a repo+tag and registry
         def repoAndTag
         def registrySpec
         def firstPart = registryImageArray[0]
         if (registryImageArray.length == 1 ) {
             //No slash - implicit registrySpec
             repoAndTag = firstPart
         }
         else if ( firstPart.contains(".") || firstPart.contains(":") ) {
             registrySpec = firstPart
             repoAndTag = registryImageArray[1]
         }
         else {
             repoAndTag = rawImageSpec
         }

         def imageSpec
         def versionSpec

         if (repoAndTag.contains(":")) {
             //is explicit version/tag
             def imageRefParts = repoAndTag.split(":")
             imageSpec = imageRefParts[0]
             versionSpec = imageRefParts[1]

         }
         else {
             //is implicit version
             imageSpec = repoAndTag
         }

         result.registry = registrySpec
         result.image = imageSpec
         result.version = versionSpec

         return result
     }
}
