package org.ovirt.engine.core.utils.hostinstall;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.utils.log.Log;
import org.ovirt.engine.core.utils.log.LogFactory;
import org.ovirt.engine.core.utils.FileUtil;

public class OpenSslCAWrapper {
    public final boolean SignCertificateRequest(String requestFileName, int days, String signedCertificateFileName) {
        log.debug("Entered SignCertificateRequest");
        boolean returnValue = true;
        String signRequestBatch = Config.resolveSignScriptPath();
        if (FileUtil.fileExists(signRequestBatch)) {
            Integer signatureTimeout = Config.<Integer> GetValue(ConfigValues.SignCertTimeoutInSeconds);
            String[] command_array =
                    createCommandArray(signatureTimeout, signRequestBatch, requestFileName, days,
                            signedCertificateFileName);
            returnValue = runCommandArray(command_array, signatureTimeout);
        } else {
            log.error(String.format("Sign certificate request file '%s' not found", signRequestBatch));
            returnValue = false;
        }
        log.debug("End of SignCertificateRequest");
        return returnValue;
    }

    /**
     * Runs the SignReq.sh script
     * @param command_array
     * @param signatureTimeout
     * @return whether or not the certificate signing was successful
     */
    private boolean runCommandArray(String[] command_array, int signatureTimeout) {
        boolean returnValue = true;
        String outputString = null;
        String errorString = null;
        BufferedReader stdOutput = null;
        BufferedReader stdError = null;
        try {
            log.debug("Running Sign Certificate request script");
            Process process = getRuntime().exec(command_array);
            stdOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
            stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            int exitCode = 0;
            boolean completed = false;
            for (int x = 0; x < signatureTimeout; x++) {
                try {
                    Thread.sleep(1000);
                    exitCode = process.exitValue();
                    completed = true;
                    break;
                } catch (IllegalThreadStateException e) {
                    // keep going
                } catch (InterruptedException ie) {
                    // keep going
                }
            }
            outputString += readAllLines(stdOutput);
            errorString += readAllLines(stdError);
            if (!completed) {
                process.destroy();
                returnValue = false;
                log.error("Sign Certificate request script killed due to timeout");
                logOutputAndErrors(outputString, errorString);
            } else if (exitCode != 0) {
                returnValue = false;
                log.error("Sign Certificate request failed with exit code " + exitCode);
                logOutputAndErrors(outputString, errorString);
            } else {
                log.debug("Successfully completed certificate signing script");
            }
        } catch (IOException e) {
            log.error("Exception signing the certificate", e);
            logOutputAndErrors(outputString, errorString);
            returnValue = false;
        } finally {
            // Close the BufferedReaders
            try {
                if (stdOutput != null) {
                    stdOutput.close();
                }
                if (stdError != null) {
                    stdError.close();
                }
            } catch (IOException ex) {
                log.error("Unable to close BufferedReader");
            }
        }
        return returnValue;
    }

    /**
     * Returns a formatted String from the content of the given bufferedReader
     * @param bufferedReader
     */
    private String readAllLines(BufferedReader bufferedReader) {
        String tempString;
        StringBuilder returnString = new StringBuilder();
        try {
            while ((tempString = bufferedReader.readLine()) != null) {
                returnString.append(tempString).append('\n');
            }
        } catch (IOException e) {
            log.error("IOException while trying to read from buffer", e);
        }
        return returnString.toString();
    }

    /**
     * Prepares a String array in order to run the SignReq.sh script
     * @param signatureTimeout
     * @param signRequestBatch
     *            the script name
     * @param requestFileName
     * @param days
     * @param signedCertificateFileName
     * @return the command_array ready to run
     */
    private String[] createCommandArray(Integer signatureTimeout,
            String signRequestBatch,
            String requestFileName,
            int days,
            String signedCertificateFileName) {
        log.debug("Building command array for Sign Certificate request script");
        String baseDirectoryPath = Config.resolveCABasePath();
        String keystorePass = Config.<String> GetValue(ConfigValues.keystorePass);
        String lockfileName = Config.<String> GetValue(ConfigValues.SignLockFile);
        Calendar yesterday = Calendar.getInstance();
        yesterday.add(Calendar.DATE, -1);
        SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmssZ");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));
        String[] command_array = { signRequestBatch, requestFileName, signedCertificateFileName, "" + days,
                baseDirectoryPath, format.format(yesterday.getTime()), keystorePass, lockfileName, "" + (signatureTimeout / 2) };
        log.debug("Finished building command array for Sign Certificate request script");
        return command_array;
    }

    /**
     * Logs the given output to DEBUG, and the given errors to ERROR
     * @param output
     *            The formatted output from the script
     * @param errors
     *            The formatted errors from the script
     */
    private void logOutputAndErrors(String output, String errors) {
        if (errors != null && errors.length() != 0) {
            log.error("Sign Certificate request script errors:\n" + errors);
        }
        if (output != null && output.length() != 0) {
            log.debug("Sign Certificate request script output:\n" + output);
        }
    }

    /**
     * This method is meant to enable testing, since Runtime cannot be mocked statically
     */
    protected Runtime getRuntime() {
        return Runtime.getRuntime();
    }

    private static Log log = LogFactory.getLog(OpenSslCAWrapper.class);
}
