# Copyright (c) 2021-2022 VMware, Inc. All rights reserved. -- VMware Confidential

"""
VSM component module following the B2B patching principles
"""

import errno
import os
import sys
from shutil import move, copyfile

from extensions import extend, Hook
from l10n import msgMetadata as _T, localizedString as _
import vcsa_utils
import logging

from patch_specs import Requirements, PatchInfo, RequirementsResult
from patch_errors import UserError

from reporting import getProgressReporter

logger = logging.getLogger(__name__)

PATH_ETC_VMWAREVSM = "/etc/vmware-vsm/"

MARKER_UPDT = PATH_ETC_VMWAREVSM + "vsm_updt"

PATH_VSMSPEC_PROPS = PATH_ETC_VMWAREVSM + "vsmspec.properties"
PATH_VSMSPEC_PROPS_TMP = PATH_VSMSPEC_PROPS + ".tmp"

FILE_VSM_PROXY_CONF = "vsm-proxy.conf"
PATH_ETC_PROXY_EP_CONF = "/etc/vmware-rhttpproxy/endpoints.conf.d/"
PATH_VSM_PROXY_CONF = PATH_ETC_PROXY_EP_CONF + FILE_VSM_PROXY_CONF
PATH_VSM_PROXY_CONF_TMP = PATH_ETC_VMWAREVSM + FILE_VSM_PROXY_CONF + ".tmp"

PATH_VSM_PROP = PATH_ETC_VMWAREVSM + 'vsm.properties'
PATH_VSM_PROPS_RPMNEW = PATH_VSM_PROP + ".rpmnew"
PATH_VSMSPEC_PROPS_RPMNEW = PATH_ETC_VMWAREVSM + "vsmspec.properties" + ".rpmnew"


def _copy_file(src, dst):
    """
    copy a file to tmp file for non-destructive upgrade use.
    """
    import shutil
    shutil.copyfile(src, dst)
    if not os.path.isfile(dst):
        raise Exception('Failed to copy file %s to %s' % (src, dst))

def _cleanup_tmp():
    """
    cleanup tmp config files.
    """
    if os.path.isfile(PATH_VSMSPEC_PROPS_TMP):
        os.remove(PATH_VSMSPEC_PROPS_TMP)
    if os.path.isfile(PATH_VSM_PROXY_CONF_TMP):
        os.remove(PATH_VSM_PROXY_CONF_TMP)

def _cleanup_markers():
    """
    cleanup b2b / ndu marker files.
    """
    if os.path.isfile(MARKER_UPDT):
        os.remove(MARKER_UPDT)

def _cleanup_rpmnew():
    """
    cleanup vsm rpmnew files.
    b2b / rdu updates the content as needed.
    """
    if os.path.isfile(PATH_VSM_PROPS_RPMNEW):
        os.remove(PATH_VSM_PROPS_RPMNEW)
    if os.path.isfile(PATH_VSMSPEC_PROPS_RPMNEW):
        os.remove(PATH_VSMSPEC_PROPS_RPMNEW)

@extend(Hook.Discovery)
def discover(ctx):
    """DiscoveryResult discover(PatchContext sharedCtx) throw UserUpgradeError"""
    # Modify copies of config files run on source VC if possible.
    # Disable replication for catalog.zip. it will be created on target.
    replicationConfig = {
        MARKER_UPDT: MARKER_UPDT,
        PATH_VSMSPEC_PROPS_TMP: PATH_VSMSPEC_PROPS,
        PATH_VSM_PROXY_CONF_TMP: PATH_VSM_PROXY_CONF,
    }
    return vcsa_utils.getComponentDiscoveryResult("vsm",
        displayName=_(_T("VSM.displayName", "VMware vService Manager")),
        replicationConfig=replicationConfig)


@extend(Hook.Requirements)
def collectRequirements(ctx):
    """RequirementsResult collectRequirements(PatchContext ctx)"""
    mismatches = []
    requirements = Requirements()
    patchInfo = PatchInfo()
    return RequirementsResult(requirements, patchInfo, mismatches)


@extend(Hook.Validation)
def validate(ctx):
    """ValidationResult validate(PatchContext ctx)"""
    pass


@extend(Hook.Expand)
def expand(ctx):
    """void expand(PatchContext sharedCtx) throw UserError"""
    # Expand is run for NDU and B2B update and replaces prepatch.
    logger.info('VSM expand phase started')

    progressReporter = getProgressReporter()
    progressReporter.updateProgress(0, _(_T("vsm.expand.begin",
                                            'Expand phase started')))

    # Expand hook is always run.
    # Expand hook runs on source VC during NDU.
    # NDU replicates only the ndu marker to target.
    logger.info('Placed marker for vsm update')
    open(MARKER_UPDT, "a").close()

    # copy vsm proxy cfg files to vmware-vsm as tmp for upgrade modification.
    progressReporter.updateProgress(30)
    try:
        _copy_file(PATH_VSMSPEC_PROPS, PATH_VSMSPEC_PROPS_TMP)
        _copy_file(PATH_VSM_PROXY_CONF, PATH_VSM_PROXY_CONF_TMP)
        logger.info('Copied vsm proxy config files to tmp')
    except Exception as ex:
        logger.error('Exception copying files: %s' % ex.message)
        err = _(_T("install.vsm.expand.copyspec.failed",
                   "Failed to copy vsm proxy config files."))
        raise UserError(cause=err)

    # patch_utils require modules that may not be present in the source system.
    # Those can't be imported when running discovery on the source system.
    from .vsm_install_utils import \
        is_proxy_conf_uptodate, update_proxy_spec_file, create_vsm_proxy_file

    # vsmspec.properties and vsm-proxy.conf.tmp patching.
    try:
        if is_proxy_conf_uptodate():
            logger.info("Vsm proxy configuration up to date")
        else:
            update_proxy_spec_file(PATH_VSMSPEC_PROPS_TMP)
            vsm_proxy_conf_file = PATH_VSM_PROXY_CONF_TMP
            create_vsm_proxy_file(PATH_VSMSPEC_PROPS_TMP, vsm_proxy_conf_file, PATH_VSM_PROP)
            logger.info('Completed vsm proxy config tmp modification')
    except Exception as vex:
        logger.error('Exception creating vsm proxy config: %s' % vex.message)
        err = _(_T("install.vsm.expand.createconf.failed",
                   "Failed to create vsm proxy config files."))
        raise UserError(cause=err)

    progressReporter.updateProgress(70)

    logger.info('VSM expand phase completed')
    progressReporter.updateProgress(100, _(_T("vsm.expand.complete",
                                              'Expand phase completed')))


@extend(Hook.Contract)
def contract(ctx):
    """void contract(PatchContext ctx) throw UserError"""
    # contract hook used in future to cleanup any properties added in expand.
    logger.info('VSM contract phase started')
    progressReporter = getProgressReporter()
    progressReporter.updateProgress(0, _(_T("vsm.contract.begin",
                                            'Contract VSM started')))

    _cleanup_rpmnew()
    _cleanup_markers()

    logger.info('VSM contract phase completed')
    progressReporter.updateProgress(100, _(_T("vsm.contract.complete",
                                              'Contract VSM completed')))


@extend(Hook.Revert)
def revert(ctx):
    """void revert(PatchContext ctx) throw UserError"""
    # cleanup tmp config files and markers.
    logger.info('VSM revert phase started')
    progressReporter = getProgressReporter()
    progressReporter.updateProgress(0, _(_T("vsm.revert.begin",
                                            'Reverting VPXD started')))

    _cleanup_tmp()
    _cleanup_markers()

    logger.info('VSM revert phase completed')
    progressReporter.updateProgress(100, _(_T("vsm.revert.complete",
                                              'Reverting VSM completed')))
