# Copyright 2018-2021 VMware, Inc.
# All rights reserved. -- VMware Confidential

import os
import sys
import logging
import traceback
from importlib import import_module
from . import utils

vmware_python_path = os.getenv('VMWARE_PYTHON_PATH')
if vmware_python_path and os.path.exists(vmware_python_path):
    sys.path.append(vmware_python_path)

from extensions import extend, Hook
from patch_specs import RequirementsResult, Mismatch
import vcsa_utils
from l10n import msgMetadata as _T, localizedString as _

from cis.defaults import get_component_home_dir
from cis.tools import wait_for_install_parameter
from cis.vecs import vmafd_machine_id

CISREG_PATH = os.path.join(get_component_home_dir('cm'), 'bin')
sys.path.append(CISREG_PATH)
from cloudvmcisreg import cloudvm_sso_cm_register, VecsKeyStore, get_property

from fss_utils import getTargetFSS  # pylint: disable=E0401

sys.path.append(os.path.join(os.path.dirname(__file__), "patches"))
patches = [
    # In order to update from version 0 to version 1, execute patch_01.py
    # The first value is the version or patch level
    # The second value is the name of the python module that runs the patch
    # The third value is whether to apply the patch unconditionally every time
    # The fourth value is whether to apply the patch in NDU Contract step. If false, the patch will not be applied inside NDU context.
    #   If not in NDU context, then fourth value is ignored.
    ("1", "patch_01", True,  False),
    ("2", "patch_02", False, False),
    ("3", "patch_03", False, False),
    ("4", "patch_04", False, False),
    ("5", "patch_05", False, True)
]

TOKEN_SERVICE_CISREGSPEC_FILE = "/usr/lib/vmidentity/install/tokenservice/tokenservice-ls-spec.properties"
TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER = "%s.unregister" % TOKEN_SERVICE_CISREGSPEC_FILE
ACTIVE_DIRECTORY_CISREGSPEC_FILE = \
    "/usr/lib/vmidentity/install/activedirectory/activedirectory-ls" \
    "-spec.properties"
SOLUTION_USER_NAME = "machine"

logger = logging.getLogger(__name__)

NDU_LIMITED_DOWNTIME_FSS = "NDU_Limited_Downtime"

EXTERNAL_IDP_DESC = _T('vmidentity.patch.externalidp.vecs.description',
                       'The upgrade has detected an AD FS external identity provider configured on this system. After the upgrade completes, connections to this identity provider no longer use the JRE truststore. If you have not already done so, add the identity provider certificates from the JRE truststore to the Trusted Root Certificates Store (VECS) to avoid login failures after upgrade.')
EXTERNAL_IDP_RES = _T('vmidentity.patch.externalidp.vecs.resolution',
                      'Please read KB81807 for more information. https://kb.vmware.com/s/article/81807')


@extend(Hook.Discovery)
def discover(ctx):
    '''DiscoveryResult discover(PatchContext sharedCtx)throw UserUpgradeError'''
    logger.info("Executing Discovery Hook for sts")
    return vcsa_utils.getComponentDiscoveryResult("sts")


@extend(Hook.Requirements)
def collectRequirements(ctx):
    logger.info("Executing Requirements Hook for VMware Identity STS Service")
    _mismatches = []

    if utils.get_external_idp_configured():
        _mismatches.append(
            Mismatch(text=_(EXTERNAL_IDP_DESC),
                     resolution=_(EXTERNAL_IDP_RES),
                     severity=Mismatch.WARNING))

    return RequirementsResult(mismatches=_mismatches)


@extend(Hook.Patch)
def Patch(ctx):
    # This hook is invoked after the rpms are installed.

    if not vcsa_utils.isDisruptiveUpgrade(ctx) and getTargetFSS(NDU_LIMITED_DOWNTIME_FSS):
        return

    logger.info("Executing Patch Hook for VMware Identity STS Service")

    try:
        _do_incremental_patching(ctx, False)
    except BaseException as e:
        logger.info('Failed to complete incremental patching ' +
                    'for VMware Identity STS Service')
        traceback.print_exc()
        raise e


def _do_incremental_patching(ctx, isInNDUContext):
    for ver, module_path, unconditional, isPatchForNDU in patches:
        logger.info("Checking whether patch '%s' (Version '%s') is needed"
                    % (module_path, ver))

        # If we are in NDU context, then check which patches are marked to be apply inside NDU context.
        # if we are NOT in NDU context, then apply all patches.
        to_apply = False
        if isInNDUContext:
          if isPatchForNDU == isInNDUContext:
            to_apply = True
        else:
          to_apply = True

        if to_apply:
            # Check the current patch level, or unconditional flag.
            # If patch level is below the current patch, then apply the patch.
            current_ver_lower = utils.is_patch_needed(ver)
            if unconditional or current_ver_lower:
                msg = "Applying patch '%s'" % module_path
                if unconditional:
                    msg += " unconditionally"
                logger.info(msg)
                mod = import_module(module_path)
                mod.do_patching(ctx)
                logger.info("Patch '%s' applied" % module_path)
            if current_ver_lower:
                utils.update_patch_level(ver)
        else:
            logger.info("Patch '%s' is skipped. Because NDU context is %s and patch is %s for NDU" % (module_path, isInNDUContext, isPatchForNDU))


def get_soluser_id():
    return '%s-%s' % (SOLUTION_USER_NAME, vmafd_machine_id())


def get_soluser_ownerId():
    soluser_id = get_soluser_id()
    domain_name = wait_for_install_parameter('vmdir.domain-name')
    return '%s@%s' % (soluser_id, domain_name)


def unregisterTokenServiceWithLookupService():
    if not os.path.isfile(TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER):
        logger.info('Registration file to unregister does not exist: %s. '
                    'Skipping unregistering tokenservice.' %
                    TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER)
        return

    # Only attempt the unregistration if the spec file has a valid service id
    serviceid = getPropertyInFile(TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER, 'cmreg.serviceid')
    if not serviceid or len(serviceid) < 1:
        logger.info('Registration file to unregister is missing cmreg.serviceid property: %s. '
                    'Skipping unregistering tokenservice and removing registration file.' %
                     TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER)
        os.remove(TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER)
        return

    logger.info('Unregistering Token Service with Lookup Service.')
    keystore = VecsKeyStore(SOLUTION_USER_NAME)
    dynVars = {'solution-user.ownerId' : get_soluser_ownerId(),
               'solution-user.name' : get_soluser_id(),
               'tokenservice.metadata-path' :
               '/usr/lib/vmidentity/install/tokenservice',
    }

    try:
        cloudvm_sso_cm_register(keystore,
                                TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER,
                                SOLUTION_USER_NAME,
                                regOp='unregister',
                                local=True,
                                dynVars=dynVars)

        # prevent attempting to unregister in a following update
        os.remove(TOKEN_SERVICE_CISREGSPEC_FILE_UNREGISTER)
    except BaseException as e:
        logger.error('Failed to unregister VMware Token Service with Lookup Service.')
        raise e

    logger.info('Successfully unregistered Token Service with the Lookup Service.')


def registerTokenServiceWithLookupService():
    logger.info('Registering Token Service with Lookup Service.')

    keystore = VecsKeyStore(SOLUTION_USER_NAME)

    dynVars = {'solution-user.ownerId': get_soluser_ownerId(),
               'solution-user.name': get_soluser_id(),
               'tokenservice.metadata-path':
               '/usr/lib/vmidentity/install/tokenservice',
    }

    try:
        cloudvm_sso_cm_register(keystore,
                                TOKEN_SERVICE_CISREGSPEC_FILE,
                                SOLUTION_USER_NAME,
                                local=True,
                                dynVars=dynVars)
    except BaseException as e:
        logger.error('Failed to register VMware Token Service with Lookup Service.')
        raise e

    logger.info('Successfully registered Token Service with the Lookup Service.')


def registerActiveDirectoryServiceWithLookupService():
    logger.info('Registering Active Directory Service with Lookup Service.')
    keystore = VecsKeyStore(SOLUTION_USER_NAME)

    dynVars = {'solution-user.ownerId' : get_soluser_ownerId(),
               'solution-user.name' : get_soluser_id(),
               'activedirectory.metadata-path' :
                   '/usr/lib/vmidentity/install/activedirectory',
               }

    try:
        cloudvm_sso_cm_register(keystore,
                                ACTIVE_DIRECTORY_CISREGSPEC_FILE,
                                SOLUTION_USER_NAME,
                                local=True,
                                dynVars=dynVars)
    except BaseException as e:
        logger.error('Failed to register Active Directory Service with Lookup Service.')
        raise e

    logger.info('Successfully registered Active Directory Service with the Lookup Service.')


def getPropertyInFile(filepath, prop):
    with open(filepath, 'rt') as f:
        for line in f:
            l = line.strip()
            if l and not l.startswith('#'):
                key_value = l.split('=')
                key = key_value[0].strip()
                if key == prop:
                    return key_value[1].strip()
    return None


@extend(Hook.Contract)
def contract(ctx):
    '''void contract(Contract ctx) throw UserError'''

    if not getTargetFSS(NDU_LIMITED_DOWNTIME_FSS):
        return

    logger.info("Executing Contract Hook for sts")

    try:
        _do_incremental_patching(ctx, True)
    except BaseException as e:
        logger.info('Failed to complete incremental patching ' +
                    'for VMware Identity STS Service, within NDU context')
        traceback.print_exc()
        raise e

    try:
        unregisterTokenServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to unregister VMware Token Service with Lookup Service.')
        traceback.print_exc()
        raise e

    try:
        registerTokenServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to register VMware Token Service with Lookup Service.')
        traceback.print_exc()
        raise e

    try:
        registerActiveDirectoryServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to register Active Directory Service with Lookup Service.')
        traceback.print_exc()
        raise e

    logger.info("Exiting Contract Hook for sts")


@extend(Hook.OnSuccess)
def OnSuccess(ctx):
    '''Call back for post upgrade cleanup'''

    if not vcsa_utils.isDisruptiveUpgrade(ctx) and getTargetFSS(NDU_LIMITED_DOWNTIME_FSS):
        return

    logger.info("Executing OnSuccess Hook for sts")

    try:
        unregisterTokenServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to unregister VMware Token Service with Lookup Service.')
        traceback.print_exc()
        raise e

    try:
        registerTokenServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to register VMware Token Service with Lookup Service.')
        traceback.print_exc()
        raise e

    try:
        registerActiveDirectoryServiceWithLookupService()
    except BaseException as e:
        logger.info('Failed to register Active Directory Service with Lookup Service.')
        traceback.print_exc()
        raise e

    logger.info("Exiting OnSuccess Hook for sts")

