#!/usr/bin/python
# Copyright 2015 VMware, Inc.  All rights reserved. -- VMware Confidential
#
"""
PatchRunner is a standalone console application targeted to
orchestrate the patching process of existing vCSA. In addition to that, it
1. aggregates the vCSA patching script results and provide single accumulated results.
2. respects the inter-component script dependencies and call every script in right time
"""
import sys
import os
import logging
import argparse
import traceback
import json

rootDir = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.join(rootDir, 'py'))
sys.path.append(os.path.join(rootDir, 'libs'))
sys.path.append(os.path.join(rootDir, 'libs', 'sdk'))
sys.path.append(os.path.join(rootDir, 'libs', 'feature-state'))

pythonVerLib = "python{0}".format(sys.version_info[0])
sys.path.insert(0, os.path.join(rootDir, 'libs', pythonVerLib, 'site-packages'))

import vmware.patching.utils.resource_utils as resources
resources.setRootDirectory(rootDir)


from os_utils import createDirectory
from vmware.patching.utils import logging_utils
from vmware.patching.config import settings

from vmware.patching.phases import discoverer
from vmware.patching.phases import validator
from vmware.patching.phases import system_preparator
from vmware.patching.phases import prepatcher
from vmware.patching.phases import patcher

logger = logging.getLogger(__name__)

def usage(argv):
    applName = os.path.basename(argv[0])
    print(__doc__ % applName)

def _setupLogging(logOnScreen):
    '''Setups the logging facility.
    '''

    loggingData = settings.loggingData

    # Creates log dir if does not exist
    createDirectory(loggingData.directory)

    # Setup the logging
    logFilePath = os.path.join(loggingData.directory, loggingData.rootName)
    logging_utils.setupLogging(logFilePath,
                               getattr(logging, loggingData.level),
                               logOnScreen)

def _loadAnswers(answerFile):
    '''Loads the customer answers from a file.

    @param answerFile: Location of a file where all customers answers are persited
      in json format
    @type answerFile: str

    @return: The customer answers as dictionary
    @rtype: dict
    '''
    if not answerFile:
        return {}

    if not os.path.exists(answerFile):
        raise ValueError("Given answer file '%s' does not exist", answerFile)

    if not os.path.isfile(answerFile):
        raise ValueError("Given answer file '%s' is not a regular file",
                         answerFile)

    try:
        with open(answerFile) as fp:
            return json.load(fp)
    except:
        logger.exception("Invalid answer file input")
        raise ValueError("Given answer file '%s' is not a regular json file",
                         answerFile)

def _createCommandLineArgumentParser():
    '''Creates the command line arguments. The tool support several sub-commands:
      discovery, validate, prepatch and patch.
    '''

    defaultStageDir=os.path.abspath(os.path.join(rootDir, os.pardir, os.pardir, settings.stageDir))
    defaultOutputFile=os.path.join(defaultStageDir, settings.outputFile)
    rootParser = argparse.ArgumentParser(prog="PatchRunner")
    subParsers = rootParser.add_subparsers(title="sub-commands")

    class StageDirAction(argparse.Action):
        '''Action that set correct default value to outputFile if none is provided
        '''
        # adapted from documentation
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, values)
            dOutputFile = getattr(namespace, 'outputFile')
            if dOutputFile == defaultOutputFile:
                dOutputFile = os.path.join(values, settings.outputFile)
            setattr(namespace, 'outputFile', dOutputFile)

    # discovery action parser
    discoveryParser = subParsers.add_parser("discovery",
                                        help="Discovery of vCSA patching components")

    discoveryParser.add_argument('-o', '--outputFile',
                           help="Specifies the output file where the output will be stored into",
                           default=defaultOutputFile)
    discoveryParser.add_argument('-d', '--stageDir',
                           help="Specifies the temporary directory where the intermediate data will be stored into",
                           default=defaultStageDir,
                           action=StageDirAction)
    discoveryParser.add_argument('-c', '--components',
                           help="Specifies which components should take part of "
                           "the patch process. If not specified, all of them will "
                           "take part.")
    discoveryParser.add_argument('-l', '--locale',
                           help="Specifies the client locale",
                           default='en')
    discoveryParser.add_argument('--disableStdoutLogging', help="Specifies whether to log on stdout",
                             action="store_true")
    discoveryParser.set_defaults(callback=discoverer.discover,
                                 operation="Discovery of vCSA patching components")

    # validator action parser
    validateParser = subParsers.add_parser("validate",
                                        help="Validate the patching of vCSA")

    validateParser.add_argument('-u', '--userData',
                           help="Path to a file which contains user answers in "
                                "json format",
                           type = _loadAnswers,
                           default={})
    validateParser.add_argument('-o', '--outputFile',
                           help="Specifies the output file where the output will be stored into",
                           default=defaultOutputFile)
    validateParser.add_argument('-d', '--stageDir',
                           help="Specifies the temporary directory where the intermediate data will be stored into",
                           default=defaultStageDir,
                           action=StageDirAction)
    validateParser.add_argument('--disableStdoutLogging', help="Specifies whether to log on stdout",
                             action="store_true")
    validateParser.set_defaults(callback=validator.validate,
                             operation="Validate vCSA components")

    # system preparation action parser
    systemPreparationParser = subParsers.add_parser("system-prepare",
                                        help="Prepare the system for the patching of vCSA")

    systemPreparationParser.add_argument('-u', '--userData',
                           help="Path to a file which contains user answers in "
                                "json format",
                           type = _loadAnswers,
                           default={})
    systemPreparationParser.add_argument('-o', '--outputFile',
                           help="Specifies the output file where the output will be stored into",
                           default=defaultOutputFile)
    systemPreparationParser.add_argument('-d', '--stageDir',
                           help="Specifies the temporary directory where the intermediate data will be stored into",
                           default=defaultStageDir,
                           action=StageDirAction)
    systemPreparationParser.add_argument('--disableStdoutLogging', help="Specifies whether to log on stdout",
                             action="store_true")
    systemPreparationParser.set_defaults(callback=system_preparator.prepare,
                             operation="System prepare for vCSA components")

    # prepatch action parser
    prepatchParser = subParsers.add_parser("prepatch",
                                        help="Orchestrate the pre-patching of vCSA")

    prepatchParser.add_argument('-u', '--userData',
                           help="Path to a file which contains user answers in "
                                "json format",
                           type = _loadAnswers,
                           default={})
    prepatchParser.add_argument('-o', '--outputFile',
                           help="Specifies the output file where the output will be stored into",
                           default=defaultOutputFile)
    prepatchParser.add_argument('-d', '--stageDir',
                           help="Specifies the temporary directory where the intermediate data will be stored into",
                           default=defaultStageDir,
                           action=StageDirAction)
    prepatchParser.add_argument('--disableStdoutLogging', help="Specifies whether to log on stdout",
                             action="store_true")
    prepatchParser.set_defaults(callback=prepatcher.prepatch,
                             operation="Pre-patch vCSA")

    # patch action parser
    patchParser = subParsers.add_parser("patch",
                                        help="Orchestrate the patching of vCSA")

    patchParser.add_argument('-u', '--userData',
                           help="Path to a file which contains user answers in "
                                "json format",
                           type = _loadAnswers,
                           default={})
    patchParser.add_argument('-o', '--outputFile',
                           help="Specifies the output file where the output will be stored into",
                           default=defaultOutputFile)
    patchParser.add_argument('-d', '--stageDir',
                           help="Specifies the temporary directory where the intermediate data will be stored into",
                           default=defaultStageDir,
                           action=StageDirAction)
    patchParser.add_argument('--disableStdoutLogging', help="Specifies whether to log on stdout",
                             action="store_true")
    patchParser.set_defaults(callback=patcher.patch,
                             operation="Patch vCSA")

    return rootParser

def main(args):
    # Parse the command line arguments
    cmdParser = _createCommandLineArgumentParser()
    parsedArgs = cmdParser.parse_args(args)

    # Setup logging
    _setupLogging(not parsedArgs.disableStdoutLogging)

    # Extract the callback arguments
    callback, operation = parsedArgs.callback, parsedArgs.operation
    callbackArgs = parsedArgs.__dict__
    del callbackArgs["callback"], callbackArgs["operation"], callbackArgs["disableStdoutLogging"]

    # Call the operation callback
    logger.info("Start executing %s with following arguments 'PatchRunner.py %s'",
                operation, " ".join(args))
    succeed = False
    try:
        succeed = callback(**callbackArgs)

        if succeed:
            logger.info("%s succeeded", operation)
        else:
            logger.error("%s failed", operation)
    except Exception: #pylint: disable=W0703
        logger.exception("%s got unhandled exception", operation)

    return 0 if succeed else 1

if __name__ == "__main__":
    try:
        sys.exit(main(sys.argv[1:]))
    except Exception: #pylint: disable=W0703
        rootLogger = logging.getLogger('')
        if not rootLogger.handlers:
            sys.stderr.write('Caught unhandled exception. Error: %s\n' % traceback.format_exc())
        else:
            logger.exception('Caught unhandled exception.')

        sys.exit(1)