# Copyright 2016 Vmware, Inc. All rights reserved. -- VMware Confidential
# This is a utility class that provides the ability to manipulate rpms
# installed on a linux machine.
#

import logging
import re

from transport import executeCommand
from transport.local import LocalOperationsManager

logger = logging.getLogger(__name__)

localOpsManager = LocalOperationsManager()

MEMORY_MULTIPLIERS = {
    'b': 1,
    'kb': 1024,
    'mb': 1024 ** 2,
    'gb': 1024 ** 3
}

def isRpmInstalled(rpmName):
    '''
    Check if rpm is installed on the system or not

    @param rpmName: The rpm name
    @type: str

    '''
    if not rpmName:
        return False

    result = executeCommand(localOpsManager, ['rpm', '-q', rpmName])
    return result.exitCode == 0

def installRpms(rpmList, forced=False, test=False):
    '''
    Installs the list of rpms on the Linux system. If any errors happen none
    rpms will be installed

    @param rpmList: List of rpm names to be installed, full path to them and
      .rpm ext is needed.
    @type rpmList: list

    @param forced: see rpm -- force. Ignores conflicts.
    @type forced: bool

    @param test: see rpm --test. Just report if it will be succsess or not. Does
      not install
    @type test: bool

    @return: True if it was successful, False otherwise
    '''
    result = _installRpmsHelper(rpmList, forced=forced, test=test)
    return result is None or result.exitCode == 0

def _installRpmsHelper(rpmList, forced=False, test=False):
    '''
    Installs the list of rpms on the Linux system. If any errors happen none
    rpms will be installed

    @param rpmList: List of rpm names to be installed, full path to them and
      .rpm ext is needed.
    @type rpmList: list

    @param forced: see rpm -- force. Ignores conflicts.
    @type forced: bool

    @param test: see rpm --test. Just report if it will be succsess or not. Does
      not install
    @type test: bool

    @return: The rpm process result, None if rpmList is empty
    @rtype: ProcessResult
    '''
    logger.info('Installing %s rpms on the system.', len(rpmList))

    if not rpmList:
        return None

    command = ['rpm', '-Uhv']

    if forced:
        command.append('--force')
    if test:
        command.append('--test')

    command += rpmList

    result = executeCommand(localOpsManager, command)
    success = result.exitCode == 0
    logger.info('Rpm installation was %s,exitCode %s, stdout %s, stderr %s', \
                'successful' if success else 'not successful', \
                result.exitCode, result.stdout, result.stderr)
    return result

def rebuildRpmDb():
    '''
    Rebuilds the RPM DB as we encounter removeRpms methods getting stuck when we
    upgrade from Photon 1 to Photon3
    '''

    logger.info('Rebuilding RPM db')
    result = executeCommand(localOpsManager, ['rpm', '-vv', '--rebuilddb'])
    success = result.exitCode == 0
    logger.info('Rpm rebuild was %s, stdout %s, stderr %s',
                'successful' if success else 'not successful',
                result.stdout, result.stderr)

def removeRpms(rpmList):
    '''
    Removes the list of rpms from the Linux system. If there are install rpms that depend
    on any of those rpms and won't be removed the whole process is aborted and no
    rpms are removed.

    @param rpmList: List of rpm names to be removed. If a rpm is not installed
    it will be ignored
    @type rpmList: list

    @return: None if it was successful, list of rpms that cannot be removed
    '''

    # This step is needed when we try to upgrade from Photon 1 to Photon3
    import platform
    os_version = platform.linux_distribution() #pylint: disable=W1505
    if os_version and os_version[0].strip() == 'VMware Photon OS'\
                  and os_version[1] == '3.0':
        rebuildRpmDb()

    logger.info('Removing %s rpms from the system.', len(rpmList))

    rpmList = [ rpm for rpm in rpmList if isRpmInstalled(rpm)]

    if not rpmList:
        return None

    logger.info('Rpm list to remove %s', rpmList)

    command = ['rpm', '-e'] + rpmList

    result = executeCommand(localOpsManager, command)
    success = result.exitCode == 0
    logger.info('Rpm removal was %s, stdout %s, stderr %s', 'successful' if success else 'not successful', result.stdout, result.stderr)
    return None if success else rpmList

def validateRpmsRemoval(rpmList):
    '''
    Validates if list of rpms can be removed from the Linux system.

    @param rpmList: List of rpm names to be validate. If a rpm is not installed
    it will be ignored
    @type rpmList: list

    @return: True if it can, False if it cannot due to dependency issues
    '''
    logger.info('Validating if %s rpms can be removed from the system.', rpmList)
    rpmList = [ rpm for rpm in rpmList if isRpmInstalled(rpm)]

    if not rpmList:
        return True

    logger.info('List of rpms to remove: %s', rpmList)

    command = ['rpm', '-e', '--test'] + rpmList

    result = executeCommand(localOpsManager, command)
    success = result.exitCode == 0 and not result.stderr
    logger.info('Rpm removal will be %s, stdout %s, stderr %s', 'successful' if success else 'not successful', result.stdout, result.stderr)
    return success

def _convertSize(size):
    '''
    Converts the size given in a format number and metric to MB.

    @param size: The size as a string containing number and metric e.g. 1KB
    @type size: str

    @return: The size converted to MB
    @rtype: float
    '''

    m = re.search(r'\D', size)
    pos = m.start()
    return (MEMORY_MULTIPLIERS[size[pos:].lower()] * int(size[:pos])) / MEMORY_MULTIPLIERS['mb']

def getInsufficientStorageForRpmInstallation(rpmList):
    '''
    Checks if there are any insufficiencies for the rpm installation. 
    If there is enough storage - returns an empty dict
    else - dict with the missing storage in each partition in MB

    @return: dict
    '''

    if not rpmList:
        return {}

    processResult = _installRpmsHelper(rpmList, test=True)
    result = {}
    if processResult and processResult.stderr:
        # Match the message pattern to find if there are storage issues
        pattern = r'installing package .* needs (.*?) on the (.*?) filesystem'

        reqList = re.findall(pattern, processResult.stderr)

        # Reverse the tuples and convert the size to MB
        reqList = [(k, _convertSize(v)) for v, k in reqList]

        # Create the dict with the right keys the proper values will be added later
        result = dict(reqList)

        # Get the biggest insufficiency for each partition
        for partition in result.keys():
            result[partition] = max([v for k, v in reqList if k == partition])
    if result:
        logger.info('Insufficient storage space for rpm installation.')

    return result
