#!/usr/bin/env python
#
# Copyright 2017-2021 VMware, Inc.  All rights reserved. -- VMware Confidential
#
"""
This module contains helper functions for get/set the vSphere Client version.
"""

import logging
import os
import re

from transport import getCommandOutput
from transport.local import LocalOperationsManager
from .patches.file_util import get_property_from_file
from .patches.path_constants import BUILD_INFO_FILE, H5C_CONFIG_DIR, \
    H5C_PATCH_VERSION_FILE, VSPHERE_UI_CISREG_FILE

logger = logging.getLogger(__name__)

VSPHERE_UI_EXECUTED_PATCHES_TEMP_FILE = 'vsphere_ui_currently_executed_patches.txt'
VSPHERE_UI_EXPANDED_PATCHES_TEMP_FILE = 'vsphere_ui_currently_expanded_patches.txt'

VSPHERE_UI_PRE_UPGRADE_CONFIG_FILE = "vsphere_ui_pre_upgrade_cfg_file.txt"
VSPHERE_UI_SOURCE_VERSION_PROP = "vsphere_ui_source_version"
VSPHERE_UI_SOURCE_CLN_PROP = "vsphere_ui_source_cln"
VSPHERE_UI_CISREG_VERSION_PROP = 'serviceVersion'

localOpsManager = LocalOperationsManager()


def get_client_version_from_rpm():
    """
    Returns the *current* vSphere UI rpm version and CLN. Both are extracted from the currently
    installed vSphere UI RPM. This method may return different results at the different stages
    of the patching process.
    """
    command = ['rpm', '-q', 'vsphere-ui']
    cmd_out = getCommandOutput(localOpsManager, command)
    # expected output is something like "vsphere-ui-7.0.2.00000-8727809.noarch"
    version_search = re.search(r'vsphere-ui-(.*?)-(.*?)\.noarch', cmd_out)
    if version_search is None:
        logger.error('Cannot extract vSphere UI version and CLN from %s' % cmd_out)
        raise Exception('Unable to get vSphere UI version and CLN')
    return version_search.group(1), version_search.group(2)


def get_client_version_from_build_info_file():
    """
    Parse {BUILD_INFO_FILE} and read the vSphere Client rpm version.

    :return: Version of the vSphere Client rpm file
    :rtype: str
    """

    if os.path.isfile(BUILD_INFO_FILE):
        logger.debug('Reading version from file: %s' % BUILD_INFO_FILE)
        with open(BUILD_INFO_FILE, 'r') as file_in:
            for line in file_in:
                if line.startswith('vsphere_h5client:vsphere-ui-'):
                    # Line looks like this:
                    # vsphere_h5client:vsphere-ui-6.5.0.0-9557036.noarch.rpm
                    version_str = line.replace('vsphere_h5client:vsphere-ui-', '')
                    version_str = version_str.replace('.noarch.rpm', '')
                    version_str = version_str.replace('-', '.')
                    version_str = version_str.strip()
                    return version_str
    else:
        logger.warn('File %s does not exist' % BUILD_INFO_FILE)

    return None


def get_service_version_from_cisreg_file():
    """
    Returns the *serviceVersion* property from {VSPHERE_UI_CISREG_FILE}
    If the file or property are not found, returns None.
    """
    if os.path.isfile(VSPHERE_UI_CISREG_FILE):
        return get_property_from_file(VSPHERE_UI_CISREG_FILE, VSPHERE_UI_CISREG_VERSION_PROP)
    else:
        logger.warn('File %s does not exist' % VSPHERE_UI_CISREG_FILE)
    return None


def isCloudVc():
    """
    Determines if this is a cloud vCenter.
    """
    webclient_properties_file = os.path.join(H5C_CONFIG_DIR, "webclient.properties")
    vmc_mode_prop = str(get_property_from_file(webclient_properties_file, "vmc.mode")).lower()
    is_cloud_vc = 'true' == vmc_mode_prop
    return is_cloud_vc


def isVMCGateway():
    """
    Determines if H5C is running in VMC Gateway appliance.
    """
    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.warn('File %s does not exist' % BUILD_INFO_FILE)
    return False


def is_patch_executed(patch_name, patch_version):
    """
    Try to find the patch name:version in {H5C_PATCH_VERSION_FILE}.

    :param patch_name: String
    :param patch_version: LooseVersion
    :return: Whether the patch has already been executed during previous upgrade
    :rtype: bool
    """

    if (os.path.exists(H5C_PATCH_VERSION_FILE)) and \
            (os.path.isfile(H5C_PATCH_VERSION_FILE)):
        logger.debug(
            'Reading patch execution status from file: %s' % H5C_PATCH_VERSION_FILE)
        with open(H5C_PATCH_VERSION_FILE, 'r') as file_in:
            if ((patch_name + ':' + str(patch_version)) in file_in.read()):
                logger.info(
                    'Patch: %s with version %s has already been executed during previous upgrade. No need to execute it again.' % (
                        patch_name, str(patch_version)))
                return True
            else:
                logger.info(
                    'Patch: %s with version %s was not executed during previous upgrade.' % (
                        patch_name, str(patch_version)))
                return False
    else:
        return False


def is_patch_executed_during_current_upgrade(patch_name, patch_version, patch_context):
    """
    Try to find the patch name:version in {VSPHERE_UI_EXECUTED_PATCHES_TEMP_FILE}.

    :param patch_name: String
    :param patch_version: LooseVersion
    :return: Whether the patch has been executed during the current upgrade
    :rtype: bool
    """

    currently_executed_patches = os.path.join(patch_context.stageDirectory, VSPHERE_UI_EXECUTED_PATCHES_TEMP_FILE)
    if (os.path.exists(currently_executed_patches)) and \
            (os.path.isfile(currently_executed_patches)):
        logger.debug(
            'Reading patch execution status from file: %s' % currently_executed_patches)
        with open(currently_executed_patches, 'r') as file_in:
            if ((patch_name + ':' + str(patch_version)) in file_in.read()):
                logger.info(
                    'Patch: %s with version %s was executed during the current upgrade.' % (
                        patch_name, str(patch_version)))
                return True
    return False


def mark_patch_as_executed(patch_name, patch_version, patch_context):
    """
    Save the status in {H5C_VERSION_FILE} and in the patch stage directory.

    :param patch_name: String
    :param patch_version: LooseVersion

    :param patch_context: Context given by the patch framework
    :type patch_context: PatchContext
    """

    if not os.path.exists(H5C_CONFIG_DIR):
        logger.info('Creating folder %s' % H5C_CONFIG_DIR)
        os.makedirs(H5C_CONFIG_DIR)

    logger.info(
        'Saving vSphere Client patch execution status to %s' % H5C_PATCH_VERSION_FILE)
    with open(H5C_PATCH_VERSION_FILE, 'a') as file_out:
        file_out.write(patch_name + ':' + str(patch_version))

    # Temp file that contains the executed patches on the current upgrade
    currently_executed_patches = os.path.join(patch_context.stageDirectory, VSPHERE_UI_EXECUTED_PATCHES_TEMP_FILE)
    logger.info(
        'Saving vSphere Client patch execution status to %s' % VSPHERE_UI_EXECUTED_PATCHES_TEMP_FILE)
    with open(currently_executed_patches, 'a') as file_out:
        file_out.write(patch_name + ':' + str(patch_version))
    logger.info('Done')


def mark_patch_as_expanded(patch_name, patch_version, patch_context):
    """
    Save the patch_name:patch_version in the patch stage directory

    :param patch_name: String
    :param patch_version: LooseVersion
    :param patch_context:  Context given by the patch framework
    """

    currently_expanded_patches = os.path.join(patch_context.stageDirectory, VSPHERE_UI_EXPANDED_PATCHES_TEMP_FILE)
    logger.info('Saving vSphere Client patch expanded status to %s' % currently_expanded_patches)
    with open(currently_expanded_patches, 'a') as file_out:
        file_out.write(patch_name + ':' + str(patch_version))


def is_patch_expanded_during_current_upgrade(patch_name, patch_version, patch_context):
    """
    Try to find the patch name:version in {VSPHERE_UI_EXPANDED_PATCHES_TEMP_FILE}.

    :param patch_name: String
    :param patch_version: LooseVersion
    :param patch_context:  Context given by the patch framework
    :return: Whether the patch has been expanded during the current upgrade
    :rtype: bool
    """

    expanded_patches_file = os.path.join(patch_context.stageDirectory, VSPHERE_UI_EXPANDED_PATCHES_TEMP_FILE)
    if (os.path.exists(expanded_patches_file)) and \
            (os.path.isfile(expanded_patches_file)):
        logger.debug(
            'Reading patch expand status from file: %s' % expanded_patches_file)
        with open(expanded_patches_file, 'r') as file_in:
            if (patch_name + ':' + str(patch_version)) in file_in.read():
                logger.info(
                    'Patch: %s with version %s was expanded during the current upgrade.' % (
                        patch_name, str(patch_version)))
                return True
    return False
