/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2016. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2023. 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.argocd

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 ArgoCDHelper {

    private CommandHelper ch
    private String script
    private ArrayList globalArgs = [
      "--auth-token",
      "--client-crt",
      "--client-crt-key",
      "--config",
      "--core",
      "--grpc-web",
      "--grpc-web-root-path",
      "--header",
      "-H",
      "--http-retry-max",
      "--insecure",
      "--kube-context",
      "--logformat",
      "--loglevel",
      "--plaintext",
      "--port-forward",
      "--port-forward-namespace",
      "--server",
      "--server-crt"]

    /**
    *  @param workDir The working directory for the CommandHelper CLI
    *  @param scriptPath The full path to the ArgoCD CLI script
    */
    public ArgoCDHelper(File workDir, String scriptPath) {
        if (scriptPath) {
            this.script = scriptPath
        }
        else {
            this.script = 'argocd'
        }
        ch = new CommandHelper(workDir)
    }

    /**
    *  @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 stdOut = new StringBuffer()
        StringBuffer stdErr = new StringBuffer()
        proc.waitForProcessOutput(stdOut, stdErr)
        proc.out.close()
        return [stdOut.toString(), stdErr.toString()]
    }

    /**
    *  @param server The url of the ArgoCD server
    *  @param username The username to authenticate with the ArgoCD server
    *  @param password The password to authenticate with the ArgoCD server
    *  @param flags A newline-separated list of flags to set when running the login command.
    */
    public void login(String server,
                      String username,
                      String password,
                      String flags)
    {
        ArrayList args = []
        args << script
        args << 'login'
        args << server
        if (username) {
            args << '--username=' + username
            args << '--password=' + password
        }
        if (flags) {
            setFlags(args, flags)
        }

        try {
            ch.runCommand('[Action] Logging into ArgoCD server...', args) { Process proc ->
                def (String stdOut, String stdErr) = captureCommand(proc)
                println ('[Stderr output]')
                println(stdErr)
                println ('[Stdout output]')
                println(stdOut)
            }
        }
        catch (IOException ioe) {
            throw ioe
            System.exit(1)
        }
    }

    /**
    *  @param args The list of arguments to add to the configuration options
    *  @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 message The message to output before running the command
    *  @param arguments An ArrayList of argocd arguments to be executed by the command prompt
    *  @param closure The closure to interact with the Process IO
    */
    public void runCommand(String message, ArrayList args, Closure closure) {
        ArrayList largs = args.collect()
        largs.add(0, script)
        ch.runCommand(message, largs, closure)
    }

    /**
    *  @param message The message to output before running the command
    *  @param args An ArrayList of argocd arguments to be executed by the command prompt
    *  @param closure The closure to interact with the Process IO
    */
    public int runCommandWithExitCode(String message, ArrayList args, Closure closure) {
        ArrayList largs = args.collect()
        largs.add(0, script)
        ch.ignoreExitValue(true)
        return ch.runCommand(message, largs, closure)
    }

    /**
    *  Parse the given argocd arguments and return a list of all the
    *  global args found within
    *
    *  @param args A newline separated String of argocd arguments to parse
    *
    *  Return a newline separated String of global argocd args and values
    */
    public String getGlobalArgs(String args) {
        def globalArgsFound = new StringBuffer()
        if (args) {
            def argList = args.split("[\r\n]+")
            for (String arg : argList) {
                // if arg doesn't start with dash, assume it's a flag value
                if (!arg.startsWith('-')) {
                   globalArgsFound.append(arg + "\n")
                   continue
                }
                for (globalArg in globalArgs) {
                    if (arg.startsWith(globalArg)) {
                       globalArgsFound.append(arg + "\n")
                       break
                    }
                }
            }
            return globalArgsFound.toString()
        }
    }

    /**
	 *  @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
	 }
}
