package com.urbancode.air.plugins.sqlplus

/*
* Licensed Materials - Property of IBM* and/or HCL**
* UrbanCode Deploy
* (c) Copyright IBM Corporation 2002, 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
*/
import java.util.regex.Pattern
import java.util.regex.Matcher

import org.apache.commons.io.filefilter.WildcardFileFilter
import org.apache.commons.io.comparator.NameFileComparator
import org.apache.commons.io.FileUtils
import org.apache.commons.io.LineIterator

import com.urbancode.air.AirPluginTool

public class SQLPlusHelper {
    final File workDir = new File('.').canonicalFile
    final File AGENT_HOME = System.getenv("AGENT_HOME") ? new File(System.getenv("AGENT_HOME").toString() +
        File.separator + "var" + File.separator + "temp") : workDir
    String connectionString
    String oracleHome

    public SQLPlusHelper(
        String user,
        String password,
        String passScript,
        String oracleHome,
        String connectionID)
    {
        // If user doesn't supply a password, evaluate password script
        if (!password || "".equals(password)) {
            File passFile = new File(passScript)
            if (passFile.isFile() && passFile.canExecute()) {
                Process readFile = passFile.absolutePath.execute()
                password = readFile.in.text
            }
        }

        // Verify authorization credentials are specified
        // Use oracle wallet if neither user and password are specified
        if (!user && !password){
            println "[Ok] The User name and Password are empty. Using Oracle Wallet."
            // Fail if user/password, and oracleHome are not specified.
            if (!oracleHome){
                println "[Error] Authorization credentials must be specified."
                println "[Possible Solution] Specify both 'User name' and 'Password' for traditional login."
                println "[Possible Solution] Specify 'Oracle Home', and leave 'User name' and 'Password' blank, to use " +
                        "Oracle Wallet authentication. Oracle Wallet must have been previously configured outside of IBM UrbanCode Deploy."
                System.exit(1)
            }
        }
        // If XOR User and Password, then crash
        else if ((user && !password) || (!user && password)) {
            println "[Error] Authorization credentials must be specified."
            println "[Possible Solution] Specify both 'User name' and 'Password' for traditional login."
            println "[Possible Solution] Specify 'Oracle Home', and leave 'User name' and 'Password' blank, to use " +
                    "Oracle Wallet authentication. Oracle Wallet must have been previously configured outside of IBM UrbanCode Deploy."
            System.exit(1)
        }

        if (password && !password.contains('"')) {
            // apply double-quotes to passwords if not already present
            // quoting is mandatory for certain password content
            // double-quote is not allowed in passwords
            password = "\"${password}\""
        }

        this.oracleHome = oracleHome
        this.connectionString = "${user}/${password}@${connectionID}"
    }

    public String runSQLFile(
        String sqlPlusExecutable,
        String sqlFileName,
        ListIterator<String> sqlArgsIterator,
        List<String> envVars)
    {
        // Create and run the sqlplus command
        def cmdArgs = [sqlPlusExecutable, this.connectionString]

        println ""
        println "************************************************************************************************"

        String formattedFileName = sqlFileName // File name for printing
        if (formattedFileName.contains('.')) {
            formattedFileName = formattedFileName.substring(0, formattedFileName.indexOf('.')) + ".sql"
        }
        print "[Action] Executing : " + cmdArgs.join(' ') + " @" + formattedFileName
        cmdArgs << '@'+sqlFileName

        if (sqlArgsIterator.hasNext()) {
            String nextArg = sqlArgsIterator.next()

            /* Get each individual arg by spaces or by quotations */
            Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(nextArg);
            while (m.find()) {
                print " " + m.group(1)
                cmdArgs << m.group(1).replaceAll("\"", "")
            }
            println()
        }

        def proc
        if (oracleHome) {
            println "Oracle Home: ${oracleHome}"
            def env = []
            System.getenv().each {
                env << it.key + '=' + it.value
            }
            for (var in envVars) {
                env << var
            }
            env << "ORACLE_HOME=$oracleHome"
            proc = cmdArgs.execute(env, workDir)
        }
        else {
            proc = cmdArgs.execute()
        }
        proc.withWriter { writer ->
            writer.write("exit${System.getProperty("line.separator")}")
        }

        def out = new ByteArrayOutputStream()
        def err = new ByteArrayOutputStream()
        proc.waitForProcessOutput(out, err)
        println out
        println err
        proc.waitFor()
        if (proc.exitValue() != 0) {
            throw new RuntimeException("Process Failed!")
        }

        return out.toString()
    }

    public checkOutputForErrors(String output, boolean failOnORA) {
        if (Pattern.compile("PLS-\\d{5}").matcher(output).find()) {
            println "[Error] SQL compilation error! See SQLPlus Error Documentation to see what action is required."
            System.exit(1)
        }
        if (Pattern.compile("SP2-\\d{4}").matcher(output).find()) {
            println "[Error] SQLPlus error! See SQLPlus Error Documentation to see what action is required."
            System.exit(1)
        }
        // Match ORA- followed by at least five digits (e.g. ORA-12345)
        if ((failOnORA) && (Pattern.compile("ORA-\\d{5}").matcher(output).find())) {
            println "[Error] SQLPlus Database Message! See SQLPlus Error Documentation to see what action is required."
            System.exit(1)
        }
    }

    public validateEnvVars(List<String> envVars) {
        // Iterate through the additionalEnvVars property and confirm validity
        println "Environment Variables :"
        for (var in envVars) {
            println "\t" + var
            def pair = var.split('=')
            if (pair.length > 2 || !pair[0].trim() || !var.contains('=')) {
                println "[Error] Invalid environment variable given."
                println "[Possible Solution] Environment variable pair does not contain an '=' chracter to separate the variable and value."
                println "[Possible Solution] Environment variable pair contains more than one '=' character."
                println "[Possible Solution] The environment variable is empty or null."
                println "[Possible Solution] Delimit each variable and value with an '=' and separate each pair with a new line or comma."
                System.exit(1)
            }
        }
    }

    public getScriptsWithErrorHandling(List<String> sqlFilePaths) {
        // Create temporary files which include error-handling, since SQLPlus exits with code 0 by default
        List<File> editedFiles = []
        sqlFilePaths.each { sqlFilePath ->
            File sqlFile = new File(sqlFilePath)
            File temp = File.createTempFile(sqlFilePath, '.txt', AGENT_HOME)
            temp.deleteOnExit()

            LineIterator iterator = FileUtils.lineIterator(sqlFile, "UTF-8")
            try {
                while (iterator.hasNext()) {
                    String line = iterator.nextLine()
                    def fileRegex = /@@[a-zA-Z0-9.-_]+[\n|\s]*/
                    Matcher match = line =~ fileRegex

                    for (int i = 0; i < match.count; i++) {
                        def subscriptPath = match[i].trim().toString().substring(2)
                        subscriptPath = subscriptPath.replaceAll(";", "")
                        File subscriptFile = new File(subscriptPath)
                        File tempSubscriptFile = File.createTempFile(subscriptPath, '.txt', AGENT_HOME)
                        tempSubscriptFile.deleteOnExit()

                        LineIterator subscriptIterator = FileUtils.lineIterator(subscriptFile, "UTF-8")
                        try {
                            while (subscriptIterator.hasNext()) {
                                String subscriptLine = subscriptIterator.nextLine()
                                tempSubscriptFile << subscriptLine + System.getProperty("line.separator")
                            }
                        }
                        finally {
                            LineIterator.closeQuietly(subscriptIterator)
                        }

                        String findText = "@@"+ subscriptPath
                        String replaceText = "@@\""+ tempSubscriptFile.absolutePath +"\""

                        line = line.replace(findText, replaceText)
                    }

                    temp << line + System.getProperty("line.separator")
                }
            }
            finally {
                LineIterator.closeQuietly(iterator)
            }

            temp << System.getProperty("line.separator") << "SHOW ERRORS;"
            editedFiles.add(temp.absolutePath)
        }
        println("Edited files after Error-handling")
        return editedFiles
    }

    public List<String> getExistingScripts(List<String> sqlFiles) {
        def existingScripts = new ArrayList<String>()

        // Iterate through each given file and confirm existence
        sqlFiles.each { sqlFileName ->
            File sqlFile = new File(sqlFileName)
            sqlFileName = sqlFileName.trim()
            if (sqlFileName.contains("*") || sqlFileName.contains("?")) {
                def wildcardSqlFile = new File(sqlFileName)
                def wildcard = wildcardSqlFile.getName()
                FilenameFilter filter = new WildcardFileFilter(wildcard)
                def parentDir = wildcardSqlFile.getCanonicalFile().getParentFile()

                if (parentDir == null) {
                    parentDir = workDir
                }
                def filterFiles = parentDir.listFiles((FilenameFilter) filter)
                def matchingFiles = []
                if (filterFiles != null) {
                    matchingFiles = Arrays.asList(filterFiles)
                }
                Comparator<File> nameComparator = NameFileComparator.NAME_COMPARATOR
                Collections.sort(matchingFiles, nameComparator)

                matchingFiles.each { matchingFile ->
                    existingScripts.add(matchingFile.getCanonicalPath())
                }

            } else if (!sqlFile.exists()) {
                throw new RuntimeException("[Error] SQL File ${sqlFile.absolutePath} does not exist!")
            } else {
                existingScripts.add(sqlFileName)
            }
        }

        return existingScripts
    }
}
