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

import logging
import os
import json
import shutil
from fss_utils import getTargetFSS
from vcsa_utils import isDisruptiveUpgrade

NETDUMPER_PROPERTIES = '/etc/vmware-netdump/firstboot/netdump.properties'
CFG_SERVICE_VERSION_KEY = 'serviceVersion'
DEPLOYMENT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))

_RPM_MERGE_FILE_EXT = '.rpmnew'
_CFG_MERGE_FILE = 'netdumper.newsysconfig'

_sourceVersion = ''
_logger = logging.getLogger(__name__)

def getSourceVersion():
    ''' Loads current component version.
    @return current version
    '''
    global _sourceVersion
    if not _sourceVersion:
        _sourceVersion = getPropFromCfgFile(NETDUMPER_PROPERTIES,
                                            CFG_SERVICE_VERSION_KEY)
        assert(_sourceVersion)
    return _sourceVersion


def setSourceVersion(version):
    ''' Set the netdumper version.
    @prop version: version to be set to
    '''
    updateCfgFile(NETDUMPER_PROPERTIES, [(CFG_SERVICE_VERSION_KEY, version)])


def getPropFromCfgFile(fullFilePath, prop):
    """   Read a property from the given cfg file(i.e. 'key=value')
    @param fullFilePath: full file path
    @param prop: The property that value needs to be extracted
    @return The value of the given @prop
    """
    assert(os.path.exists(fullFilePath))

    with open(fullFilePath, 'r') as f:
        for line in f:
            if line and not line.startswith('#'):
                index = line.find('=')
                if index > 0:
                    key = line[:index].strip()
                    if key == prop:
                        return line[index + 1:].strip()
    return None


def updateCfgFile(fullFilePath, propInfoListToChange):
    """   Update given props in the wanted cfg file(i.e. 'key=value')
    @param fullFilePath: full file path
    @param propInfoListToChange: List of tuples of prop/value to update
    """
    assert(os.path.exists(fullFilePath))

    data = _loadCfgFile(fullFilePath)
    assert(data)

    # Find and replace
    for (prop, newValue) in propInfoListToChange:
        (ind, _) = _getIndAndElemFromList(data, prop)
        assert(ind >= 0)
        data[ind] = (prop, newValue)

    # Save the merged results in the file
    _writeToCfgFile(fullFilePath, data)


def mergeCfgFile(fullFilePath, preserveProperties, ctx):
    ''' Merge config files(i.e. 'key=value') by preserving the values
    mentioned in @preserveProperties from the source if present in the new file

    @prop fullFilePath: full file path
    @prop preserveProperties: list of properties that values need to be stored
    @prop ctx: PatchContext
    '''
    # If the new config file is created, then file has been edited by the user.
    # Merge the changes from source to destination depending
    # on the preserveProperties. '''

    fullFilePathNew = ''
    if not getTargetFSS("NDU_Limited_Downtime") or isDisruptiveUpgrade(ctx):
        fullFilePathNew = fullFilePath + _RPM_MERGE_FILE_EXT
    else:
        fullFilePathNew = os.path.join(DEPLOYMENT_ROOT, _CFG_MERGE_FILE)

    _logger.info("New config file : " + str(fullFilePathNew))

    if os.path.exists(fullFilePathNew):
        _logger.info('Merging new netdumper config with old config.')
        oldFileData = _loadCfgFile(fullFilePath)
        newFileData = _loadCfgFile(fullFilePathNew)

        # Merge
        for prop in preserveProperties:
            (oldIdx, oldValue) = _getIndAndElemFromList(oldFileData, prop)
            (newIdx, _) = _getIndAndElemFromList(newFileData, prop)

            if oldIdx >= 0 and newIdx >= 0:
                newFileData[newIdx] = (prop, oldValue)

        # Save the merged results in the file
        if not getTargetFSS("NDU_Limited_Downtime") or isDisruptiveUpgrade(ctx):
            _writeToCfgFile(fullFilePath, newFileData)
            os.remove(fullFilePathNew)
        else:
            _writeToCfgFile(fullFilePathNew, newFileData)
            os.remove(fullFilePath)
            shutil.copyfile(fullFilePathNew, fullFilePath)

        _logger.info('Successfully merged netdumper configs.')


def _getIndAndElemFromList(propList, key):
    ''' In list of tuples of key/value returns
    the index and the value that corespond to the wanted key

    @prop propList: The list to search in
    @prop key: the key to search for
    @return List of tuples of key/value loaded from the file
    '''
    for (index, (prop, value)) in enumerate(propList):
        if (prop == key):
            return (index, value)

    return (-1, '')


def _loadCfgFile(fullFilePath):
    ''' Load given config file(i.e.'key=value') into list
    NOTE: empty line and comments are skipped. Order is preserved.

    @prop fullFilePath: full file path
    @return List of tuples of key/value loaded from the file
    '''
    assert(os.path.exists(fullFilePath))

    props = []
    with open(fullFilePath, 'r') as f:
        data = f.readlines()
        for line in data:
            if line and not line.startswith('#'):
                index = line.find('=')
                if index > 0:
                    key = line[:index].strip()
                    value = line[index + 1:].strip()
                    props.append((key.strip(), value.strip()))

    return props


def _writeToCfgFile(fullFilePath, props):
    ''' Overwrite @fullFilePath file with the given list of prop/value
    @prop fullFilePath: full file path
    @prop props: list of tuple of key/value
    '''
    assert(os.path.exists(fullFilePath))

    dataToWrite = ''
    for prop in props:
        dataToWrite += '%s=%s\n' % (prop[0], prop[1])

    with open(fullFilePath, 'w') as f:
        f.write(dataToWrite)


def mergeJsonFile(fullFilePath, preserveProperties):
    ''' Merge json files by preserving the source values mentioned in @preserveProperties
       Order is NOT preserve
    @prop fullFilePath: full file path
    @prop preserveProperties: list of properties from the source
          that values need to be stored
    '''
    fullFilePathNew = fullFilePath + _RPM_MERGE_FILE_EXT
    if os.path.exists(fullFilePathNew):
        with open(fullFilePath, 'r') as f:
            oldFile = json.load(f)

        with open(fullFilePathNew, 'r') as f:
            newFile = json.load(f)

        # Merge
        # TODO: make 'prop' to be not just root level item
        for prop in preserveProperties:
            if prop in oldFile and prop in newFile:
                newFile[prop] = oldFile[prop]

        # Save the merged results in the file
        with open(fullFilePath, 'w') as f:
            json.dump(newFile, f, indent=3)

        os.remove(fullFilePathNew)

# The key path must be present in the dictionary or KeyError exception is
# thrown


def getValueFromJsonFile(fullFilePath, key):
    ''' By given path in the json file return its value
    @prop fullFilePath: full file path
    @prop key: path of the key that value needs to be returned
    @return the wanted value as string
    '''
    with open(fullFilePath, 'r') as f:
        curDict = json.load(f)

    # Root level elem in json file is always dictionary
    isCurElemDict = True
    curList = []
    value = ''
    try:
        for subKey in key.split('.'):
            tmpElem = curDict[
                subKey] if isCurElemDict else curList[int(subKey)]

            if isinstance(tmpElem, dict):
                curDict = tmpElem
                isCurElemDict = True
            elif isinstance(tmpElem, list):
                curList = tmpElem
                isCurElemDict = False
            else:
                value = str(tmpElem)
    except Exception as e:
        _logger.exception(e)
        raise

    return value
