# Copyright 2015 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""
VMDIR patching module following the B2B patching principles
"""
import sys
import os
import json
import platform
import logging
import socket
import vcsa_utils

from . import utils
from patch_errors import InternalError
from patch_specs import DiscoveryResult, ValidationResult, Question, Mismatch, \
    Requirements, PatchInfo, RequirementsResult
from extensions import extend, Hook
from l10n import msgMetadata as _T, localizedString as _
from reporting import getProgressReporter

log = logging.getLogger(__name__)
VMDIR_PAYLOAD_DIR = os.path.dirname(__file__)

# Import patches python directory where all individual python module reside
sys.path.append(os.path.join(VMDIR_PAYLOAD_DIR, "patches"))
patches = [
]

def getUtil():
    if os.name == "nt":
        return utils.PatchUtilsWin()
    else:
        return utils.PatchUtilsLin()

def getCurrentVersion():
    return getUtil().getSourceVersion()

def getLatestVersion():
    if len(patches) > 0:
        return patches[-1][0]
    return "6.5.0.0"

def getChangesSummary():
    latestPatchScript = patches[-1][1]
    mod = __import__(latestPatchScript)
    changesSummary = None
    if hasattr(mod, 'getChanges'):
        changesSummary = mod.getChanges()

    return changesSummary

@extend(Hook.Discovery)
def discover(ctx):
    '''DiscoveryResult discover(PatchContext sharedCtx) throw UserUpgradeError'''

    return vcsa_utils.getComponentDiscoveryResult("vmdird")

@extend(Hook.Requirements)
def collectRequirements(ctx): #pylint: disable=W0613
    '''RequirementsResult collectRequirements(PatchContext sharedCtx)'''
    mismatches = []
    requirements = Requirements()
    patchInfo = PatchInfo()

    passwordQuestion = Question(userDataId="vmdir.password",
                                text=_(_T("vmdir.password.text",
                                          "Single Sign-On administrator password")),
                                description=_(_T("vmdir.password.desc",
                                                 "For the first instance of the identity domain, this is the password given to the Administrator account.  Otherwise, this is the password of the Administrator account of the replication partner.")),
                                kind=Question.PASSWORD_KIND)
    try:
        requirements = Requirements(requiredDiskSpace={"/storage/core" :
                                                       getUtil().getRequiredDiskSpace()},
                                    questions=[passwordQuestion],
                                    rebootRequired=False)
    except Exception: #pylint: disable=W0703
        log.exception("Cannot connect to vmdir service")
        mismatches.append(Mismatch(text=_(_T("vmdir.error.text",
                                             "Unable to connect to VMware Directory Service")),
                                   description=_(_T("vmdir.error.description",
                                                    "VMware Directory Service is down")),
                                   resolution=_(_T("vmdir.error.resolution",
                                                   "Please check that VMware Directory Service is running")),
                                   severity=Mismatch.ERROR))

    return RequirementsResult(requirements, patchInfo, mismatches)

@extend(Hook.Validation)
def validate(ctx): #pylint: disable=W0613
    '''ValidationResult validate(PatchContext sharedCtx)'''
    mismatches = []

    if not getUtil().validatePassword(ctx.userData['vmdir.password']):
        mismatches.append(
                Mismatch(text=_(_T("vmdir.password.error.text",
                                   "Single Sign-On administrator password is missing or incorrect")),
                         description=_(_T("vmdir.password.error.description",
                                          "Single Sign-On administrator login is required for VMware Directory Service patching.")),
                         resolution=_(_T("vmdir.password.error.resolution",
                                         "Please provide valid Single Sign-On password.")),
                         severity=Mismatch.ERROR,
                         relatedUserDataId='vmdir.password'))

    return ValidationResult(mismatches)

@extend(Hook.Prepatch)
def prePatch(ctx): #pylint: disable=W0613
    '''void prePatch(PatchContext sharedCtx) throw UserUpgradeError'''

def _postInstallScripts(ctx): #pylint: disable=W0613
    password = ctx.userData['vmdir.password']
    update_schema()
    update_vmdir(password)

def update_vmdir(password):
    # Windows not supported
    if platform.system().lower() == "windows":
        return

    util = getUtil()

    # Start service before update
    util.start_service()

    afdParamKeyPath = '[HKEY_THIS_MACHINE\\Services\\vmafd\\Parameters]'
    dirKeyPath = '[HKEY_THIS_MACHINE\\Services\\vmdir]'

    domainName = util.getRegValue(afdParamKeyPath, 'DomainName');

    path = get_vdc_upgrade_path()
    dcAccountDN = util.getRegValue(dirKeyPath, 'dcAccountDN')
    samAccount = socket.getfqdn().rstrip('\n')
    vmdir_admin_upn = '%s@%s' % ('Administrator', domainName)

    args = [path, '-H', 'localhost', '-D', vmdir_admin_upn, '-d', dcAccountDN, '-s', samAccount]
    argsdisplay =  [path, '-H', 'localhost', '-D', vmdir_admin_upn, '-d', dcAccountDN, '-s', samAccount]

    log.info("Performing vmdir update")

    (rc, stdout, stderr) = util.run_command_with_display(args, argsdisplay,
                                                               stdin=password)
    if rc != 0:
        log.error('Failed to run command : %s', argsdisplay)
        log.error(stderr)
        log.error(stdout)
        raise InternalError("Vmware Directory Service patch failed" )
    log.info(stdout)

def get_vdc_upgrade_path():
    # Windows not supported
    if platform.system().lower() == "windows":
        return

    path = os.path.join(getUtil().getInstallDir(), 'bin', 'vdcupgrade')
    return path

def get_vmdird_path():
    # Windows not supported
    if platform.system().lower() == "windows":
        return

    path = os.path.join(getUtil().getInstallDir(), 'sbin', 'vmdird')
    return path

def update_schema():
    log.info("Performing schema upgrade")

    utils = getUtil()

    path = get_vmdird_path()

    datafile = os.path.join(utils.getConfigDir(), 'vmdirschema.ldif')

    args = [path, '-u', '-c', '-s', '-f', datafile]
    argsdisplay = args

    (rc, stdout, stderr) = getUtil().run_command_with_display(args, argsdisplay)
    if rc != 0:
        log.error('Failed to run command : %s', argsdisplay)
        log.error(stderr)
        log.error(stdout)
        raise InternalError("Vmware Directory Service schema update failed" )

    log.info(stdout)
    log.info('Schema update success')

def _doIncrementalPatching(ctx):
    utils = getUtil()
    currVersion = getCurrentVersion()
    log.info('Vmdir current version: %s', currVersion)
    patchCurrentVersion = currVersion is None

    for ver, modulePath in patches:
        compResult = utils.versionCompare(currVersion, ver)
        patchCurrentVersion |= compResult < 0

        if patchCurrentVersion:
            log.info('Applying patch %s on version %s' % (ver, modulePath))
            mod = __import__(modulePath)
            mod.doPatching(ctx)
            log.info('Patch %s applied' % (modulePath))

    utils.setSourceVersion(getLatestVersion())

def set_maintainance_mode(password):

    log.info('Set maintenance mode')

    afdParamKeyPath = '[HKEY_THIS_MACHINE\\Services\\vmafd\\Parameters]'
    vmdirKey='[HKEY_THIS_MACHINE\\Services\\vmdir]'
    valName='Arguments'

    utils = getUtil()

    if utils.is_service_running(utils.getVmdirComponentName()):
        log.error('service vmdir is running while patching')
        raise InternalError("VMware Directory Service patch failed: Service is running while patching")

    domainName = utils.getRegValue(afdParamKeyPath, 'DomainName');
    vmdir_admin_upn = '%s@%s' % ('Administrator', domainName)

    # Get current arguments as a list
    currentArgs = utils.getRegValue(vmdirKey, valName)
    argList = currentArgs.split()

    # add standalone args
    args = ['-m', 'standalone']

    for arg in args:
        argList.append(arg)

    # write back args
    newArgs = ' '.join(argList)

    utils.setRegValue(vmdirKey, valName, newArgs)

    log.info('Maintenance mode success')

@extend(Hook.Patch)
def patch(ctx):
    '''void patch(PatchContext sharedCtx) throw UserUpgradeError'''

    utils = getUtil()

    progressReporter = getProgressReporter()

    progressReporter.updateProgress(0, _(_T("vmdir.patch.begin",
                                            'Start VMware Directory Service patching')))
    LwIsStarted = utils.start_lwsmd()

    # set vmdir in maintainance mode before patching
    set_maintainance_mode(ctx.userData['vmdir.password'])

    _doIncrementalPatching(ctx)
    _postInstallScripts(ctx)

    if LwIsStarted:
        utils.stop_lwsmd()

    progressReporter.updateProgress(100, _(_T("vmdir.patch.complete",
                                              'Patching VMware Directory Service completed')))

@extend(Hook.OnSuccess)
def OnSuccess(ctx):
    '''Call back for post upgrade cleanup'''
    try:
        vmdirScriptPath = '/usr/lib/vmware-vmdir/scripts/'
        sys.path.append(vmdirScriptPath)
        import vmdirResetMode

        log.info('Performing vmdir commit operation')
        vmdirResetMode.resetMode()
        log.info('vmdir commit complete')
    except:
        log.exception('vmdir commit failed')
        log.error(vmdirResetMode.LIN_FAIL_MESAGE)
