// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['fs/services/settings/CertificateService',
        'fs/model/settings/CertificateModel',
        'hp/core/EventDispatcher',
        'hp/core/Localizer'],
function(certificateService, certificateModel, EventDispatcher, localizer) { "use strict";

    var CertificatePresenter = (function() {

        var initialized = false;

        // Total timeout / length of progress bar
        var RESTART_TIMEOUT = 30000; // msec

        // How long to wait, after a checkReachable request times out, before trying again.
        var REACHABLE_DELAY = 1500; // msec

        // How long to wait, after a getTaskStatus request times out, before trying again.
        var TASK_RESULT_DELAY = 500; // msec

        // How long it takes to execute another checkReachable call
        var CHECK_REACHABLE_DELAY = 1500; //msec

        var CREATE = "createCert";
        var IMPORT = "importCert";
        
        var reachErrorCount = 0;

        // Work around for Mozilla browser to fix mix content blocker issue
        var mozillaTimeout = 4500; //msec
        /**
         * @constructor Presentation logic for the Certificate panel on the Settings Overview page.
         */
        function CertificatePresenter() {

            // Timer for host reachability
            var createCertReachableTimer;
            var importCertReachableTimer;

            // Flag indicating that we are currently polling the server for reachability.
            var checkingCreateCertReachable;
            var checkingImportCertReachable;

            // Total amount of time to wait for node to be accessible.
            var createCertTimeout;
            var importCertTimeout;

            // Timestamp when web server restart was started.
            var createCertStartTime;
            var importCertStartTime;

            // For unit testing, allow a stub of the timers created and cleared.
            var timerHost = window;

            var dispatcher = new EventDispatcher();

            // URI for the asynchronous task.
            var createCertTaskURI;
            var importCertTaskURI;

            // Forward function reference.
            var checkTaskResult;
            // Limit for server reachable failures
            var FAILURE_LIMIT = 3;

            /**
             * Stop the interval timer pinging the host when it becomes reachable.
             */
            function clearTimers(action) {
                if (action == CREATE) {
                    timerHost.clearTimeout(createCertReachableTimer);
                    createCertReachableTimer = null;
                }
                if (action == IMPORT) {
                    timerHost.clearTimeout(importCertReachableTimer);
                    importCertReachableTimer = null;
                }
            }

            /**
             * Report a failure of the web server restart to the view.
             *
             * @param {Object} errorInfo Error information
             * @param {Object} handlers Handlers for the web server restart results.
             */
            function onRestartFailed(errorInfo, action, handlers) {
                // there are two types of error returns to handle here
                // this is when we get an error from the task tracker status
                clearTimers(action);                
                if (errorInfo.taskErrors && errorInfo.taskErrors.length > 0) {                    
                    handlers.error(errorInfo.taskErrors[0].nestedErrors);
                }
                // and this is when the async REST call returns an error directly
                else if (errorInfo.message || errorInfo.recommendedActions)
                {
                    handlers.error(errorInfo);
                }
            }

            /**
             * If the timeout expires and we haven't reached the new server, throw up an error.
             *
             * @param {Object} handlers Handlers for the web server restart results.
             */
            function onRestartTimeout(action, handlers) {
                if (action == CREATE) {
                    checkingCreateCertReachable = false; // stop all reachability testing; we've given up.
                }
                if (action == IMPORT) {
                    checkingImportCertReachable = false; // stop all reachability testing; we've given up.
                }
                handlers.error({
                    message: localizer.getString('fs.settings.certificate.selfsigned.error.timeout.msg'),
                    recommendedActions: [localizer.getString('fs.settings.certificate.selfsigned.error.timeout.res')]
                });
            }

            /**
             * Handler for when the host becomes reachable.
             *
             * @param {Object} handlers Handlers for the web servrer restart results.
             */
            function onReachable(host, action, handlers, unitTest) {
                if (action == CREATE) {
                    checkingCreateCertReachable = false;
                }
                if (action == IMPORT) {
                    checkingImportCertReachable = false;
                }
                clearTimers(action);                
                if (unitTest){
                    handlers.success();
                }else{
                    checkTaskResult('', action, false, handlers);
                }
            }

            /**
             * Check whether the server is reachable.
             *
             * @param {Object} handlers Handlers for the web server restart results.
             */
            function checkReachable(host, action, handlers, unitTest) {
                var checkingReachable;
                var restartStartTime;
                var restartTimeout;
                var ssl = false;

                if (action == CREATE) {
                    createCertReachableTimer = null;
                    checkingReachable = checkingCreateCertReachable;
                    restartStartTime = createCertStartTime;
                    restartTimeout = createCertTimeout;
                }
                if (action == IMPORT) {
                    importCertReachableTimer = null;
                    checkingReachable = checkingImportCertReachable;
                    restartStartTime = importCertStartTime;
                    restartTimeout = importCertTimeout;
                }
                certificateService.checkReachable(host, ssl, {
                    success: function() {
                        onReachable(host, action, handlers, unitTest);
                    },
                    error: function() {
                        // If we've already flagged that the host is reachable by some other
                        // means, we can ignore this error.  This flag is also cleared when
                        // the overall operation times out.
                        if (!checkingReachable) {
                                return;
                        }
                        if ( $.browser.mozilla == true)
                        {
                            mozillaTimeout -= REACHABLE_DELAY;
                            if(mozillaTimeout <=0){
                                handlers.success();
                                return;
                                }
                        }

                        // If not reachable after the end of the timeout period,
                        // we'll call this a timeout.
                        if (Date.now() - restartStartTime > (restartTimeout - CHECK_REACHABLE_DELAY)) {
                            clearTimers(action);
                            onRestartTimeout(action, handlers);
                            return;
                        }

                        // Wait a while and check reachability again.
                        if (action == CREATE) {
                            createCertReachableTimer = timerHost.setTimeout(function() {
                                checkReachable(host, action ,handlers, unitTest);
                            }, REACHABLE_DELAY);
                        }
                        if (action == IMPORT) {
                            importCertReachableTimer = timerHost.setTimeout(function() {
                                checkReachable(host, action, handlers, unitTest);
                            }, REACHABLE_DELAY);
                        }
                    }
                });
            }

            /**
             * Wait until the server is reachable
             *
             * @param {Object} handlers Handlers for the results.
             */
            function waitForReachable(action, handlers) {
                if (action == CREATE) {
                    checkingCreateCertReachable = true;
                    createCertReachableTimer = timerHost.setTimeout(function() {
                        checkReachable('', action, handlers, false);
                    }, REACHABLE_DELAY);
                }
                if (action == IMPORT) {
                    checkingImportCertReachable = true;
                    importCertReachableTimer = timerHost.setTimeout(function() {
                        checkReachable('', action, handlers, false);
                    }, REACHABLE_DELAY);
                }
            }

            /**
             * Handle the task status returned from the asynchronous call.
             *
             * @param {string} host Hostname from which task status was requested, '' for current.
             * @param {TaskResource} status Task resource with status.
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @param {Object} handlers Handlers for the results.
             */
            function handleTaskStatus(host, status, action, unitTest, handlers) {
                switch (status.taskState) {
                case 'Running':
                    /* if checkTaskResult() is executed during unit testing, this will be in 
                    * a recursive loop waiting for the task state to be 'Completed', 
                    * or this will timeout; there's no means to change the task state
                    * to 'Completed' from 'Running' via mock.
                    */
                    
	                    // Job is still running, we'll poll again after an interval.
	                    timerHost.setTimeout(function() {
	                        checkTaskResult(host, action, unitTest, handlers);
	                    }, TASK_RESULT_DELAY);
                    
                    break;

                case 'Completed':
                    // Job finished successfully.
                    handlers.success();
                    break;

                default:
                    // Job failed.
                    onRestartFailed(status, action, handlers);
                    break;
                }
            }

            /**
             * Method called periodically to check the results of the running asynchronous task.
             *
             * @param {string} host Hostname of the host to request task status from, '' for current.
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @param {Object} handlers Handlers for the results.
             */
            checkTaskResult = function(host, action, unitTest, handlers) {
                var taskURI;

                if (action == CREATE) {
                    checkingCreateCertReachable = false;
                    taskURI = createCertTaskURI;
                }
                if (action == IMPORT) {
                    checkingImportCertReachable = false;
                    taskURI = importCertTaskURI;
                }
                certificateService.getTaskStatus(host, taskURI, {
                    success: function(status) {                        
                        reachErrorCount = 0;                        
                        handleTaskStatus(host, status, action, unitTest, handlers);
                    },
                    error: function(errorInfo) {
                        clearTimers(action);                                                   
                        if (errorInfo.taskErrors && errorInfo.taskErrors.length > 0) {                                                        
                                handlers.error(errorInfo.taskErrors[0].nestedErrors);                                                        
                        }
                        // and this is when the async REST call returns an error directly
                        else 
                        {
                            if(reachErrorCount == FAILURE_LIMIT)
                            {
                                reachErrorCount = 0;
                                checkCertificateChange(handlers, errorInfo);
                            }else{
                                reachErrorCount = reachErrorCount+1;
                                waitForReachable(action, handlers);   
                            }                            
                        }                                                                        
                    }
                });
            };
            
            // in waitForReachable we checked appliance using http as it was reachable we hit FAILURE_LIMIT
            // now we check using https if reachable no certificate change else certificate has changed   
            // if certificate changed CheckTaskResult failed because of it  
            // else genuine error
            // if certificate is changed no need to check certificate task status, report success 
            function checkCertificateChange(handlers, errorInfo) {
                var ssl = true;
                certificateService.checkReachable('', ssl, {
                    success: function(data) {
                        handlers.error(errorInfo);
                    },
                    error: handlers.success
                });
            }

            /**
             * The createSelfSignedCertificate call succeeded, which means the asynchronous process of
             * restarting the web server has begun.  Wait for it to complete.
             *
             * @param {Object} result Result of the createSelfSignedCertificate call.
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @param {Object} handlers Handlers for the createSelfSignedCertificate results.
             */
            function onCreateSelfSignedCertSuccess(result, unitTest, handlers) {
                createCertTaskURI = result.uri;
                handlers.running(createCertTimeout);
                /* if checkTaskResult() is executed during unit testing, this will be in 
                 * a recursive loop waiting for the task state to be 'Completed', 
                 * or this will timeout; there's no means to change the task state
                 * to 'Completed' from 'Running' via mock.
                 */
                    checkTaskResult('', CREATE, unitTest, handlers);
                
            }

            /**
             * The importSignedCertificate call succeeded, which means the asynchronous process of
             * restarting the web server has begun.  Wait for it to complete.
             *
             * @param {Object} result Result of the importSignedCertificate call.
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @param {Object} handlers Handlers for the importSignedCertificate results.
             */
            function onImportSignedCertSuccess(result, unitTest, handlers) {
                importCertTaskURI = result.uri;
                
                //Require to add timeout handler to pass unit test, otherwise unit test blocking further processing
                if(unitTest) {
                	handlers.running(importCertTimeout);
                }
                /* if checkTaskResult() is executed during unit testing, this will be in 
                 * a recursive loop waiting for the task state to be 'Completed', 
                 * or this will timeout; there's no means to change the task state
                 * to 'Completed' from 'Running' via mock.
                 */
                
                    checkTaskResult('', IMPORT, unitTest, handlers);
                
            }

            /**
             * Retrieve the certificate information
             *
             * @param {Object} handlers The success and error handler methods.
             */
            this.getCertificate = function(handlers) {
                certificateService.getCertificate({
                    success: handlers.success,
                    error: handlers.error
                });
            };

            /**
             * Create the self-signed certificate
             * @param certObject contains the attributes of a certificate
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @see onCreateSelfSignedCertSuccess().
             * @param {Object} handlers The success and error handler methods.
             */
            this.createSelfSignedCertificate = function(certObject, unitTest, handlers) {
                // How long are we going to wait before giving up?
                createCertTimeout = RESTART_TIMEOUT;
                createCertStartTime = Date.now();
                certificateService.createSelfSignedCertficate(certObject,{
                    success: function(result) {
                        onCreateSelfSignedCertSuccess(result, unitTest, handlers);
                    },
                    error: function(errorInfo) {
                      onRestartFailed(errorInfo, CREATE, handlers);
                    }
                });
            };

            /**
             * Create the CSR
             *
             * @param {Object} handlers The success and error handler methods.
             */
            this.createCSR = function(certObject, handlers) {
                certificateService.createCSR(certObject, handlers);
            };

            /**
             * Import the signed certificate
             *
             * @param {Object} Certificate data object
             * @param unitTest used to prevent checkTaskResult() from execution.
             * @see onImportSignedCertSuccess()
             * @param {Object} handlers The success and error handler methods.
             */
            this.importSignedCertificate = function(certDataObject, unitTest, handlers) {
                // How long are we going to wait before giving up?
                importCertTimeout = RESTART_TIMEOUT;
                importCertStartTime = Date.now();
                certificateService.importSignedCertficate(certDataObject,{
                    success: function(result) {
                        onImportSignedCertSuccess(result, unitTest, handlers);
                    },
                    error: function(errorInfo) {
                      onRestartFailed(errorInfo, IMPORT, handlers);
                    }
                });
            };

            this.on = function(eventName, callback) {
                dispatcher.on(eventName, callback);
            };

            this.off = function(eventName, callback) {
                dispatcher.off(eventName, callback);
            };

            var onUserLoggedInSuccess = function (data) {
                dispatcher.fire("usersLoggedInSuccess", data);
            };

            var onUsersLoggedInError = function (errorInfo) {
                dispatcher.fire("usersLoggedInError", errorInfo);
            };

            this.init = function() {
                if (!initialized) {
                    certificateModel.on("usersLoggedInSuccess",onUserLoggedInSuccess);
                    certificateModel.on("usersLoggedInError",onUsersLoggedInError);
                    initialized = true;
                }
            };

            this.usersLoggedIn = function() {
                certificateModel.usersLoggedIn();
            };

            this.getOtherUsersLoggedInList = function() {
                return certificateModel.getOtherUsersLoggedInList();
            };

            this.setOtherUsersLoggedInList = function(otherUsersLoggedInList) {
                certificateModel.setOtherUsersLoggedInList(otherUsersLoggedInList);
            };

            /**
             * @private For unit testing only
             */
            this.testCheckReachable = function(hostname, action, checking, timeout, handlers, unitTest) {
                if (action == CREATE) {
                    checkingCreateCertReachable = checking;
                    createCertStartTime = Date.now();
                    createCertTimeout = timeout;
                }
                if (action == IMPORT) {
                    checkingImportCertReachable = checking;
                    importCertStartTime = Date.now();
                    importCertTimeout = timeout;
                }
                checkReachable(hostname, action, handlers, unitTest);
            };

            /**
             * @private For unit testing only
             */
            this.testHandleTaskStatus = function(host, status, action, unitTest, handlers) {
                handleTaskStatus(host, status, action, unitTest, handlers);
            };

            /**
             * @private For unit testing only
             */
            this.clearTimersPublic = function(action){
                clearTimers(action);
            };
        }

        return new CertificatePresenter();

    }());

    return CertificatePresenter;

});
