#!/usr/bin/env python
"""
Main entry point for the program

Attributes:
    exportpath (str): Path to export directory
    logconfig (str): Path to log config (config_log.ini)
    logdir (str): Path to log directory
    logname (str): Name of the log file to use
    params (dict): dictionary of local node parameters returned from lib.utils.get_params()
    workingdir (str): holds the current working directory
"""
VERSION = "251003"

import argparse
import textwrap as _textwrap
import logging.config
import socket
from lib.utils import *
from lib.pscha import *
from lib.lsreport import *
from lib.trust import *
from lib.stale import *
from lib.rebuild import *
from lib.solutionusers import *
sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
from cis.defaults import get_cis_log_dir

workingdir = os.path.dirname(os.path.abspath(__file__))
logconfig = os.path.join(workingdir,'config_log.ini')

exportpath = os.path.join(workingdir,'exports')

logdir = os.path.join(get_cis_log_dir(), 'lsdoctor')
logname = 'lsdoctor'
params = {}

def _update_vm_support():
    """
    Utility function to include lsdoctor log directory in support bundles.
    """
    mfx = """
% Manifest name: lsdoctor
% Manifest group: VirtualAppliance
% Manifest default: Enabled
# action Options file/command
copy IGNORE_MISSING {logdir}/*
    """.format(logdir=logdir)
    vmsupportpath = os.path.join(os.environ['VMWARE_CFG_DIR'], 'vm-support','lsdoctor.mfx')
    if not os.path.exists(vmsupportpath):
        try:
            with open(vmsupportpath,"w+") as f:
                f.writelines(mfx)
            logger.debug("lsdoctor logs will be included in support bundles.")
        except:
            error_msg = "Couldn't add support bundle config file: %s" % vmsupportpath
            logger.error("You will have to collect pulse logs manually!  Error was: %s" % error_msg)
    else:
        logger.debug("%s already exists." % vmsupportpath)

def _createDirs(dir_name):
    """
    Utility function to create a directory.

    Args:
        dir_name (str): directory name

    """
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)

def _setFilename(name, file_type):
    """
    Sets filename in a helpful format

    Args:
        name (str): file name
        file_type (str): file extension

    Returns:
        str: string containing full file path.  compatible with windows and appliance
    """
    file_name = str(time.strftime(name + file_type))
    path = logdir + '/' + file_name
    path = path.replace('\\','/')
    return path

def _setDateFilename(dir_name, name, file_type):
    """
    Sets filename in a helpful format with date/time

    Args:
        dir_name (str): directory name
        name (str): filename
        file_type (str): file extension

    Returns:
        str: string containing full file path.  compatible with windows and appliance
    """
    file_name = str(time.strftime(name + "-%Y-%m-%d-%H%M%S" + file_type))
    return os.path.abspath(os.path.join(actiondir, file_name))

def loadJson(file):
    """
    Utility function to load a json file.

    Args:
        file (str): Path to json file

    Returns:
        dict: Returns the json as a dictionary
    """
    file = os.path.abspath(file)
    with open(file) as f:
        jsondata = json.load(f)
    return jsondata

def setupLogging():
    """
    Utility function to set up the logging (creates directories, sets the filename, loads log config)
    """
    _createDirs(logdir)
    _createDirs(exportpath)

    logfile = _setFilename(logname, '.log')

    logging.config.fileConfig(logconfig, defaults={'filename': logfile}, disable_existing_loggers=False)

def parameters():
    """
    Utility function to load parameters from lib.utils.get_params()

    Returns:
        dict: local node parameters as a dictionary
    """
    params = get_params()
    return params.get()

class LineWrapRawTextHelpFormatter(argparse.RawDescriptionHelpFormatter):

    """
    Utility to properly display help text with proper line wrapping.
    """

    def _split_lines(self, text, width):
        """
        Args:
            text (str): Text to wrap properly
            width (str): Width of the text

        Returns:
            str: wrapped help text
        """
        text = self._whitespace_matcher.sub(' ', text).strip()
        return _textwrap.wrap(text, width)


def prompt(retry=False):
    '''
    prompts for username and password with administrator@vsphere.local as default.

    Args:
        retry (bool, optional): flag to set if password was not provided.

    Returns:
        str: returns both username and password

    '''
    if retry == False:
        try:
            backup_check = raw_input("Have you taken offline (PSCs and VCs powered down at the same time) snapshots of all nodes in the SSO domain or supported backups?[y/n]")
        except:
            backup_check = input("Have you taken offline (PSCs and VCs powered down at the same time) snapshots of all nodes in the SSO domain or supported backups?[y/n]")
        if backup_check.lower() != 'y':
            logger.error("Please re-run after taking offline (PSCs and VCs powered down at the same time) snapshots of all nodes in the SSO domain or supported backups. ")
            sys.exit()
        else:
            logger.debug("User confirmed that offline snapshots of all nodes in the SSO domain were taken.")
    else:
        logger.info("No password was entered.  Please try again.")

    username = "administrator@" + str(params.get('domain_name'))

    # Get password with no echo
    print("")
    passwd = getpass.getpass("\nProvide password for %s: " % username)
    if len(passwd) < 1:
        prompt(retry=True)

    logger.debug("Username specified: %s" % username)
    return username, passwd

def displayWarning(change=True):
    """
    Warning message to display before any function is run.

    Args:
        change (bool, optional): Flag to indicate whether or not the function specified will change the system.
    """
    if change == True:
        WARNING = """
    WARNING:  This script makes permanent changes.  Before running, please take *OFFLINE* snapshots
    of all VC's and PSC's at the SAME TIME.  Failure to do so can result in PSC or VC inconsistencies.
    Logs can be found here: {log_dir}
    """
    else:
        WARNING="""
    ATTENTION:  You are running a reporting function.  This doesn't make any changes to your environment.
    You can find the report and logs here: {log_dir}
    """
    print(WARNING.format(log_dir=logdir))


def parse_argument():
    """
    Argument parser

    Returns:
        argparse object: Returns the argparse object.
    """
    INFOMSG = 'lsdoctor logs are located here: %s\n\
For more information, see https://kb.vmware.com/s/article/80469' % logdir

    parser = argparse.ArgumentParser(description='Lookup Service Doctor %s' % VERSION,
                                     formatter_class=
                                     LineWrapRawTextHelpFormatter,
                                     epilog=INFOMSG)

    cmd_group = parser.add_argument_group('Command', 'The action to perform')
    cmd_group_cmds = cmd_group.add_mutually_exclusive_group(required=True)
    # subparsers = parser.add_subparsers(dest='command')

    cmd_group_cmds.add_argument("-p", "--pscHaUnconfigure",
                                action='store_true',
                                help="Unconfigure PSCHA on this node.  "
                                     "Must be run on each PSC in SSO site.")

    cmd_group_cmds.add_argument("-s", "--stalefix",
                                action='store_true',
                                help="Check for stale 5.x info on a vCenter "
                                     "or PSC.  Run on each PSC and VC.")

    cmd_group_cmds.add_argument("-t", "--trustfix",
                                action='store_true',
                                help="Check for SSL trust mismatch.  "
                                     "Can be run on any PSC or VC for each "
                                     "SSO site -- Once per SSO site.")

    cmd_group_cmds.add_argument("-l", "--lscheck",
                                action='store_true',
                                help="Print report on problems in the "
                                     "SSO domain")

    cmd_group_cmds.add_argument("-u", "--solutionusers",
                                action='store_true',
                                help="Recreate vSphere solution users - Run "
                                     "on each PSC and VC")

    cmd_group_cmds.add_argument("-r", "--rebuild",
                                action='store_true',
                                help="Rebuild all services for a node.")

    # The following options allow for automated scripting; these should be
    # used for testing and non-production purposes ONLY
    #
    #   --password         SSO admin password; required for all but '-l'; used
    #                      to indicate that prompts should be suppressed
    #   --create-template  Create a new template based on the current build
    #                      (1 of 4 rebuild actions required for '-r' option)
    #   --service          Rebuild a single service; specify the service name
    #                      string as part of this param, i.e.
    #                      '--service svc_name'
    #                      (1 of 4 rebuild actions required for '-r' option)
    #   --all-services     Rebuild all services
    #                      (1 of 4 rebuild actions required for '-r' option)
    #   --restore          Restore services from backup file; specify the path
    #                      to the backup file as part of this param, i.e.
    #                      '--restore /path/to/backup/file'
    #                      (1 of 4 rebuild actions required for '-r' option)
    #   --template         Path to template file; optional for --service,
    #                      --all-services, and --create-template; if not
    #                      specified and no suitable template file is found in
    #                      the default location for the current build, an error
    #                      will be raised
    #
    # Examples ...
    #
    # Fix trust mismatch issues:
    #
    #   -t --password 'Admin!23'
    #
    # Fix stale issues with a specific template file if rebuilding is required:
    #
    #   -s --password 'Admin!23' --template "/path/to/templatefile"
    #
    # Generate template in the specified location for the current build:
    #
    #   -r --password 'Admin!23' --create-template --template "/path/to/templatefile"
    #
    # Rebuild a single service:
    #
    #   -r --password 'Admin!23' --service "applmgmt"
    #
    # Rebuild all services with specific template file:
    #
    #   -r --password 'Admin!23' --all-services --template "/path/to/templatefile"
    #
    # Restore all services from a backup:
    #
    #   -r --password 'Admin!23' --restore "/path/to/backupfile"

    parser.add_argument("--password", action='store', help=argparse.SUPPRESS)

    r_group = parser.add_argument_group()
    r_group_cmds = r_group.add_mutually_exclusive_group(required=False)

    r_group_cmds.add_argument("--create-template",
                              action='store_true',
                              help=argparse.SUPPRESS)
    r_group_cmds.add_argument("--service",
                              action='store',
                              help=argparse.SUPPRESS)
    r_group_cmds.add_argument("--all-services",
                              action='store_true',
                              help=argparse.SUPPRESS)
    r_group_cmds.add_argument("--restore",
                              action='store',
                              help=argparse.SUPPRESS)

    r_group.add_argument("--template",
                         action='store',
                         help=argparse.SUPPRESS)

    return parser


def pscHaUnconfigure(node_details, username, password):
    """
    Wrapper for lib.pscha.PscHa()

    Args:
        node_details (dict): dictionary of local node parameters obtains from lib.utils.get_params()
        username (str): local SSO admin user specified
        password (str): password for local SSO admin user
    """
    lb_check = PscHa(node_details, username, password, exportpath=exportpath)
    lb_check.check()


def staleCheck(node_details, username, password, args):
    """
    Wrapper for lib.stale.staleChecks()

    Args:
        node_details (dict): dictionary of local node parameters obtains from lib.utils.get_params()
        username (str): local SSO admin user specified
        password (str): password for local SSO admin user
        args (dict): parsed command-line arguments
    """
    # If a password arg was specified, this is an indication that automated
    # scripting mode is desired and the user should not be prompted
    if args.password:
        stale_check = staleChecks(node_details, username, password,
                                  exportpath=exportpath, noprompt=True,
                                  template_file=args.template)
    else:
        stale_check = staleChecks(node_details, username, password,
                                  exportpath=exportpath)
    stale_check.check()


def trustFix(node_details, username, password):
    """
    Wrapper for lib.trust.trustChecks()

    Args:
        node_details (dict): dictionary of local node parameters obtains from lib.utils.get_params()
        username (str): local SSO admin user specified
        password (str): password for local SSO admin user
    """
    trust_check = trustChecks(node_details, username, password)
    trust_check.check()

def lsCheck(node_details):
    """
    Wrapper for lib.lsreport.lsReport.generateReport()

    Args:
        node_details (dict): dictionary of local node parameters obtains from lib.utils.get_params()
    """
    report_name = str(time.strftime(str(socket.gethostname()) + "-%Y-%m-%d-%H%M%S"))
    output_file = _setFilename(report_name,'.json')
    report = lsReport(node_details,output_file)
    report.generateReport()

def solutionusersFix(username, password):
    """
    Wrapper for lib.solutionusers.solutionusersFix.checkAndFix()

    Args:
        username (str): local SSO admin user specified
        password (str): password for local SSO admin user
    """
    solutionusers_check=solutionusersFix(username,password)
    solutionusers_check.checkAndFix()


def rebuildServices(node_details, username, password, args):
    """
    Wrapper for lib.rebuild.Rebuilder()

    Args:
        node_details (dict): dictionary of local node parameters obtains from lib.utils.get_params()
        username (str): local SSO admin user specified
        password (str): password for local SSO admin user
        args (dict): parsed command-line arguments
    """

    # Check for machine ID and vpxd solution user mismatch
    # If there is a mismatch exit the operation.
    vmafd = VmafdClient();
    machine_id = vmafd.get_machine_id()
    vpxd_sol_user = get_vpxd_solution_user()
    if machine_id not in vpxd_sol_user:
        logger.error("Mismatch between machine_id %s and vpxd solution user %s. Please fix this before proceeding with this operation." % (machine_id, vpxd_sol_user))
        sys.exit(0)

    # If the password arg has been specified, indicating that automated
    # scripting mode is desired, a rebuild action must also be specified
    if args.password:
        if args.service:
            action = ACTION_REBUILD_SERVICE
        elif args.all_services:
            action = ACTION_REBUILD_ALL_SERVICES
        elif args.create_template:
            action = ACTION_CREATE_TEMPLATE
        elif args.restore:
            action = ACTION_RESTORE
        else:
            logger.error("Automated processing was attempted for the rebuild "
                         "command but a rebuild action was not specified")
            raise ValueError('Rebuild action missing for automated processing')

        builder = Rebuilder(node_details, username, password,
                            workingdir=workingdir,
                            rebuild_action=action,
                            service_name=args.service,
                            template_file=args.template,
                            restore_file=args.restore)
        builder.run_action()
    else:
        builder = Rebuilder(node_details, username, password,
                            workingdir=workingdir)
        builder.menu()


def main():
    """
    Main entry function.  Parses arguments and calls the appropriate wrapper.  Also displays the warning messages.
    """
    setupLogging()
    logger = logging.getLogger(__name__)

    _update_vm_support()


    global params
    params = parameters()
    argparser = parse_argument()

    args = argparser.parse_args()

    # The password argument is optional and, if present, signifies that we
    # should run in automated/scripting mode and try not to prompt the user
    username = password = None
    if args.password:
        logger.debug("Password specified. Running in automated mode.")
        username = "administrator@" + str(params.get('domain_name'))
        password = args.password

    logger.debug("##### BEGIN #####")
    logger.debug("retrieved node parameters: %s" % params)

    if args.pscHaUnconfigure:
        displayWarning()
        if params['deploytype'] == 'infrastructure':
            logger.info("You've chosen to unconfigure PSC HA on this node.  "
                        "NOTE:  Please run this script on all PSC's in the "
                        "SSO site.\n")
            if not password:
                username, password = prompt()
            pscHaUnconfigure(params, username, password)
            logger.info("Please run on other PSC's if applicable, then restart "
                        "services on all PSC's.  You can then repoint vCenter")
        else:
            logger.error("We detected that this is not an external PSC.  "
                         "Please run this option on an external PSC.")

    elif args.rebuild:
        displayWarning()
        warningmsg = """
                You have selected the Rebuild function.  This is a potentially destructive operation!
                All external solutions and 3rd party plugins that register with the lookup service will
                have to be re-registered.  For example: SRM, vSphere Replication, NSX Manager, etc.
            """
        logger.info(warningmsg)
        if not password:
            username, password = prompt()
        rebuildServices(params, username, password, args)

    elif args.stalefix:
        displayWarning()
        logger.info("You are running a check on this node for stale 5.x data. "
                    "NOTE:  Please run this script on all VC's or PSC's in the "
                    "SSO domain to be thorough.\n")
        if not password:
            username, password = prompt()
        staleCheck(params, username, password, args)
        logger.info("Please restart services on all PSC's and VC's when "
                    "you're done.")

    elif args.trustfix:
        displayWarning()
        logger.info("You are checking for and fixing SSL trust mismatches in "
                    "the local SSO site.  NOTE:  Please run this script one "
                    "PSC or VC per SSO site.\n")
        if not password:
            username, password = prompt()
        trustFix(params, username, password)
        logger.info("Please restart services on all PSC's and VC's when "
                    "you're done.")

    elif args.lscheck:
        displayWarning(change=False)
        logger.info("You are reporting on problems found across the SSO "
                    "domain in the lookup service.  This doesn't make changes.")
        lsCheck(params)

    elif args.solutionusers:
        displayWarning(change=True)
        logger.info("You are recreating vSphere solution users on this node. "
                    "Please run this script on all VCs or PSCs in the "
                    "SSO domain.\n")
        if not password:
            username, password = prompt()
        solCheck = solutionusersCheck(username, password)
        solCheck.checkAndFix()

    else:
        logger.error("\nYou didn't specify any options!  Please try again.")
        argparser.print_help()
        sys.exit()



if __name__ == '__main__':
    main()

