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

import groovy.json.JsonSlurper

import com.urbancode.air.CommandHelper
import com.urbancode.air.ExitCodeException

public class OpenShiftClient {

    private static CommandHelper ch = new CommandHelper(new File('.'))
    private static String script = 'oc'

    String openshiftServer
    String username
    String password
    String imageName
    String project
    Boolean allowInsecure
    Map<String,String> labels = new HashMap<String,String>()

    public OpenShiftClient(Properties props) {
        this.openshiftServer = props['openshiftServer'].trim()
        this.username        = props['openshiftUsername'].trim()
        this.password        = props['openshiftPassword']
        this.allowInsecure   = props['allowInsecure'].toBoolean()

        login()

        // Parse the Image Stream
        String[] openshiftImageStream = props['openshiftImageStream'].split('/')*.trim()
        if (openshiftImageStream.size() == 2) {
            this.project = openshiftImageStream.first()
            switchToProject(this.project)
        }
        this.imageName = openshiftImageStream.last()
    }

    /**
     * @return Completion confirms successful login to the OpenShift registry.
     */
    private void login() {
        ArrayList<String> args = ["login"]
        if (openshiftServer) {
            try {
                args << new URI(openshiftServer).toString()
            }
            catch (URISyntaxException ex) {
                throw new URISyntaxException("[Error] The OpenShift Server input '${openshiftServer}' does not have valid syntax.")
            }
        }
        args << "--username=${username}"
        args << "--password=${password}"
        if (allowInsecure) {
            args << "--insecure-skip-tls-verify=true"
            println "[Warning] '--insecure-skip-tls-verify=true' is set. This will make your HTTPS connections insecure. "
        }
        try{
            runCommand("[Action] Logging in to OpenShift...", args) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println (serr)
                    println (sout)
                    println ('[Error] Could not login to the OpenShift registry.')
                }
                else {
                    println (sout)
                    println ('[Ok] Successfully logged in.')
                }
            }
        }
        catch (IOException e) {
            throw new IOException ("[Error] Could not login: ${e.getMessage()}")
        }
        catch (ExitCodeException e) {
            throw new ExitCodeException ("[Error] Could not login: ${e.getMessage()}")
        }
    }

    /**
     * @return Completion confirms successful logout the OpenShift registry.
     */
    private void logout() {
        ArrayList<String> args = ["logout"]
        try{
            runCommand("[Action] Logging out of OpenShift...", args) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println (serr)
                    println (sout)
                    println ('[Error] Could not logout of the OpenShift registry.')
                }
                else {
                    println (sout)
                    println ('[Ok] Successfully logged out.')
                }
            }
        }
        catch (IOException e) {
            throw new IOException ("[Error] Could not logout: ${e.getMessage()}")
        }
        catch (ExitCodeException e) {
            throw new ExitCodeException ("[Error] Could not logout: ${e.getMessage()}")
        }
    }

    /**
    *  @return The list of tags within the given image name.
    */
    private def getTags() {
        def tags = []
        ArrayList<String> args = ["get"]
        args << "-o"
        args << "json"
        args << "is"
        args << imageName
        try {
            runCommand("[Action] Gathering all tags...", args) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println (serr)
                    println (sout)
                    println ('[Error] Could not find tags.')
                }
                else {
                    JsonSlurper slurper = new JsonSlurper()
                    def result = slurper.parseText(sout)
                    tags = result?.status?.tags
                    println ("[Ok] Found ${tags.size()} tags.")
                }
            }
        }
        catch (IOException e) {
            throw new IOException ("[Error] Could not switch projects: ${e.getMessage()}")
        }
        catch (ExitCodeException e) {
            throw new ExitCodeException ("[Error] Could not switch projects: ${e.getMessage()}")
        }
        return tags
    }

    /**
    *  @param nothing Value isn't used. Only for overloading in importVersion.groovy
    *  @return The Label assigned to the image name. Retreived on Object creation
    */
    private HashMap<String, String> getLabelsForTag(def nothing) {
        return getLabelsForTag()
    }

    /**
    *  @return The Label assigned to the image name. Retreived on Object creation
    */
    private HashMap<String, String> getLabelsForTag() {

        if (!this.labels) {
            this.labels = getLabels()
            if (!this.labels) {
                throw new Exception('[Error] Could not get image stream Labels from OpenShift.')
            }
            println ''
        }
        return labels
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's 'created' property
    */
    private Map getFirstItemFromTag(Map tag) {
        Map result
        if (tag) {
            List<Map> itemsList = tag?.items
            if (itemsList.size() > 0) {
                result = itemsList.first()
            }
        }
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's 'created' property
    */
    private String getCreatedForTag(Map tag) {
        String result
        result = getFirstItemFromTag(tag)?.created
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's dockerImageReference
    */
    private String getDockerImageReferenceForTag(Map tag) {
        String result
        result = getFirstItemFromTag(tag)?.dockerImageReference
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's full image
    */
    private String getImageForTag(Map tag) {
        String result
        result = getFirstItemFromTag(tag)?.image
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's ID
    */
    private String getIdForTag(Map tag) {
        String result
        result = getImageForTag(tag)?.split(":",2)?.last()
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's full image
    */
    private String getGenerationForTag(Map tag) {
        String result
        result = getFirstItemFromTag(tag)?.generation
        return result
    }

    /**
    *  @param tag The full tag object and items[]
    *  @return The tag's tag. Example: latest
    */
    private String getTagIdForTag(Map tag) {
        String result
        if (tag) {
            result = tag?.tag
        }
        return result
    }

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

    /**
    *  @param projectName The name of the project to switch to
    */
    public void switchToProject(String projectName) {
        def args = []
        args << 'project'
        args << projectName
        try {
            runCommand("[Action] Switching to OpenShift project ${projectName}...", args) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println (serr)
                    println (sout)
                    println ('[Error] Could not switch projects.')
                }
                else {
                    println (sout)
                    println ('[Ok] Switched projects.')
                }
            }
        }
        catch (IOException e) {
            throw new IOException ("[Error] Could not switch projects: ${e.getMessage()}")
        }
        catch (ExitCodeException e) {
            throw new ExitCodeException ("[Error] Could not switch projects: ${e.getMessage()}")
        }
    }

    /**
    *  @return A hashmap of label name, value pairs, or an empty HashMap for any failures
    */
    public HashMap<String,String> getLabels() {
        HashMap<String,String> labels = new HashMap<String,String>()
        def args = []
        args << 'describe'
        args << 'is'
        args << imageName
        try {
            runCommand("[Action] Getting Labels for image stream ${imageName}...", args) { Process proc ->
                def (String sout, String serr) = captureCommand(proc)
                if (serr) {
                    println (serr)
                    println (sout)
                    println ('[Error] Could not get Labels.')
                }
                else {
                    def labelsLine = false
                    sout.split('\n').each { line ->
                        if (labelsLine && !line.startsWith('\t')) {  // lines containing Label key=value pairs has ended
                            labelsLine = false
                        }
                        if (line.startsWith('Labels:')) {  // lines containing Label key=value pairs has started
                            labelsLine = true
                        }

                        if (labelsLine) {  // line contains Label key=value pair
                            def keyValuePair = line.substring(line.lastIndexOf('\t') + 1).split('=')
                            if (keyValuePair.size() == 2) {
                                println ("[Ok] Found Label: ${keyValuePair[0]}=${keyValuePair[1]}")
                                labels.put(keyValuePair[0], keyValuePair[1])
                            }
                            else {
                                println ("[Error] Bad Label value discarded: ${keyValuePair}")
                            }
                        }
                    }
                }
            }
        }
        catch (IOException e) {
            throw new IOException ("[Error] Could not get labels: ${e.getMessage()}")
        }
        catch (ExitCodeException e) {
            throw new ExitCodeException ("[Error] Could not get labels: ${e.getMessage()}")
        }
        finally {
            if (labels.size() > 0) {
                println ("[Ok] Found ${labels.size()} OpenShift image stream Labels, will be set as version properties for each tag.")
            }
            else {
                println ("[Ok] No OpenShift image stream Labels found.")
            }
        }
        return labels
    }
}
