# Copyright 2020 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""
The module contains common patching functionality for VMware vCenter services.
The component should act similarly as visl-integration does in firstboot
execution and should be executed just before all other components.
"""
import sys
import os
import json
import platform
import logging
import shutil
import vcsa_utils
import fss_utils
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 os_utils import executeCommand
from .relink_launchers import validate_java_launchers
from reporting import getMessageReporter

logger = logging.getLogger(__name__)
MY_PAYLOAD_DIR = os.path.dirname(__file__)

JAVA_LAUNCHER_RELINK_FAILED_TEXT = _(_T("java.launcher.relink.failed.text",
                                        "Some $VMWARE_JAVA_HOME/bin/*-launcher "
                                        "not pointing to $VMWARE_JAVA_HOME/bin/java."))

BUILD_INFO_FILE = '/etc/vmware/.buildInfo'


@extend(Hook.Discovery)
def discover(ctx):  # pylint: disable=W0613
    '''DiscoveryResult discover(PatchContext sharedCtx) throw UserUpgradeError'''

    return DiscoveryResult(displayName=_(_T("common.comp.displayName",
                                            "VMware vCenter Server Patcher")),
                           componentId="first-patching-component")

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

    return RequirementsResult(requirements, patchInfo, mismatches)

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

    return ValidationResult(mismatches)

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

def _getRootStagedDirectory(ctx):
    '''Gets location of the root staged directory. Each of its
    sub-directories are a component specific ones.

    @param ctx: Patch context shared among all patching hooks
    @type ctx: PatchContext

    @return: The full path location on the disk
    @rtype: str
    '''
    # Stage directory points to $STAGE_DIR/patch_runner/<compName> defined in
    # update_script.py
    stageDir = ctx.stageDirectory
    result = os.path.abspath(os.path.join(stageDir, os.pardir))
    return result

def _getStagedScript(ctx, scriptName):
    '''Gets location of the script downloaded to staged directory.

    @param ctx: Patch context shared among all patching hooks
    @type ctx: PatchContext

    @param scriptName: Name of the script
    @type scriptName: str

    @return: The full path location on the disk
    @rtype: str
    '''
    # Stage directory points to $STAGE_DIR/patch_runner/<compName> defined in
    # update_script.py
    stageDir = ctx.stageDirectory
    result = os.path.abspath(os.path.join(stageDir, os.pardir, os.pardir,
                                          "scripts", scriptName))
    return result

def _executeThirdPartyPrePatchScript(scriptLocation, args):
    '''Executes third party pre-patch script. The script contains update logic
    necessary to be executed just after all rpms are installed, and just before
    the components patch hook is get called.

    @param scriptLocation: Location to the script which needs to be executed
    @type scriptLocation: str

    @param args: Arguments which have to be passed to the script
    @type args: list
    '''
    logger.info("Running pre-patch script %s", scriptLocation)
    stdout, stderr, exitCode = executeCommand([scriptLocation] + args)
    logger.info("pre-patch script completed. Stdout=%s,Stderr=%s,exit-code=%s",
                stdout, stderr, exitCode)
    if exitCode:
        raise Exception("Unable to execute script %s" % scriptLocation)


def isGateway():
    ''' Indicate if the source is gateway or not
    '''
    if os.path.isfile(BUILD_INFO_FILE):
        with open(BUILD_INFO_FILE, 'r') as fp:
            if 'CLOUDVM_NAME:VMware-vCenter-Cloud-Gateway' in fp.read():
                logger.info('Running on a VMC Gateway appliance.')
                return True
            logger.info('Not running on a VMC Gateway appliance.')
            return False
    else:
        logger.warning('File %s does not exist', BUILD_INFO_FILE)
        return False


def patchFssLib(rootStageDir):
    """
    Update the HLM FSS file with values from HLM repo in case of HLM
    update. For existing gateways, create Platform FSS file from existing
    featureState.py if not present.
    :param rootStageDir:
    :return:
    """
    if isGateway():
        logger.info("Patching FSS lib on gateway after RPM install")
        logger.info("VMWARE_PYTHON_PATH being used %s ",
                    os.environ["VMWARE_PYTHON_PATH"])
        stageDir = os.path.abspath(os.path.join(rootStageDir, os.pardir))
        sys.path.append(os.path.join(stageDir, 'scripts'))
        import product_utils  # pylint: disable=E0401
        # HLM update or gateway transitioning to decoupled updates, in both
        # cases copy the FSS file from HLM bundle as featureState.py
        transition_marker_file = os.path.join(stageDir, 'decoupled_update')
        if product_utils.isHlmInstalled() \
                and (product_utils.isHlmUpdate()
                     or os.path.exists(transition_marker_file)):
            hlmFssSrc = os.path.join(stageDir, 'scripts', 'patches', 'libs',
                                           'feature-state', 'featureStateTarget.py')
            shutil.copy(hlmFssSrc, product_utils.HLM_FSS_FILE)
            logger.info("Updated HLM FSS file during HLM update")
        # Existing gateways will not have separate FSS for platform, we create
        # it for such gateway from the existing of featureState.py file
        if not os.path.exists(product_utils.PLATFORM_FSS_FILE):
            os.makedirs(os.path.dirname('/usr/lib/vmware-vmcg/site-packages/'),
                        exist_ok=True)
            shutil.copy(product_utils.HLM_FSS_FILE,
                        product_utils.PLATFORM_FSS_FILE)
            logger.info("Created Platform FSS file")


@extend(Hook.Patch)
def patch(ctx):  # pylint: disable=W0613
    '''void patch(PatchContext sharedCtx) throw UserUpgradeError'''
    # Persist changes to FSS
    fss_utils.persistTargetFssChanges()

    if vcsa_utils.isDisruptiveUpgrade(ctx):
        rootStageDir = _getRootStagedDirectory(ctx)
        patchFssLib(rootStageDir)
        logger.info("Executing pre-patch.sh as it is disruptive upgrade.")
        prePatchScript = _getStagedScript(ctx, "pre-patch.sh")
        _executeThirdPartyPrePatchScript(prePatchScript, [rootStageDir])

    # Check if all the java launchers correctly point to the current Java
    if not validate_java_launchers():
        # Not all $VMWARE_JAVA_HOME/bin/*-launcher point to $VMWARE_JAVA_HOME/bin/java
        getMessageReporter().postWarning(JAVA_LAUNCHER_RELINK_FAILED_TEXT)
