from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common.config.version import get_source_major_version
from leapp.libraries.stdlib import api, CalledProcessError


def get_leapp_packages():
    """
    Return the list of leapp and leapp-repository rpms that should be preserved
    during the upgrade.

    It's list of packages that should be preserved, not what is really
    installed.

    The snactor RPM doesn't have to be installed, but if so, we have to take
    care about that too as well to prevent broken dnf transaction.
    """
    # TODO: should we set the seatbelt and exclude leapp RPMs from the target
    # system too?
    generic = ['leapp', 'snactor']
    if get_source_major_version() == '7':
        return generic + ['python2-leapp', 'leapp-upgrade-el7toel8']

    return generic + ['python3-leapp', 'leapp-upgrade-el8toel9']


def _strip_split(data, sep, maxsplit=-1):
    """
    Just like str.split(), but remove ambient whitespaces from all items
    """
    return [item.strip() for item in data.split(sep, maxsplit)]


def _get_main_dump(context, disable_plugins):
    """
    Return the dnf configuration dump of main options for the given context.

    Returns the list of lines after the line with "[main]" section
    """

    cmd = ['dnf', 'config-manager', '--dump']

    if disable_plugins:
        for plugin in disable_plugins:
            cmd += ['--disableplugin', plugin]

    try:
        data = context.call(cmd, split=True)['stdout']
    except CalledProcessError as e:
        api.current_logger().error('Cannot obtain the dnf configuration')
        raise StopActorExecutionError(
            message='Cannot obtain data about the DNF configuration',
            details={'stdout': e.stdout, 'stderr': e.stderr}
        )

    try:
        # return index of the first item in the main section
        main_start = data.index('[main]') + 1
    except ValueError:
        raise StopActorExecutionError(
            message='Invalid DNF configuration data (missing [main])',
            details=data,
        )

    output_data = {}
    for line in data[main_start:]:
        try:
            key, val = _strip_split(line, '=', 1)
        except ValueError:
            # This is not expected to happen, but call it a seatbelt in case
            # the dnf dump implementation will change and we will miss it
            # This is not such a hard error as the one above, as it means
            # some values could be incomplete, however we are still able
            # to continue.
            api.current_logger().warning(
                'Cannot parse the dnf dump correctly, line: {}'.format(line))
            pass
        output_data[key] = val

    return output_data


def _get_excluded_pkgs(context, disable_plugins):
    """
    Return the list of excluded packages for DNF in the given context.

    It shouldn't be used on the source system. It is expected this functions
    is called only in the target userspace container or on the target system.
    """
    pkgs = _strip_split(_get_main_dump(context, disable_plugins).get('exclude', ''), ',')
    return [i for i in pkgs if i]


def _set_excluded_pkgs(context, pkglist, disable_plugins):
    """
    Configure DNF to exclude packages in the given list

    Raise the CalledProcessError on error.
    """
    exclude = 'exclude={}'.format(','.join(pkglist))
    cmd = ['dnf', 'config-manager', '--save', '--setopt', exclude]

    if disable_plugins:
        for plugin in disable_plugins:
            cmd += ['--disableplugin', plugin]

    try:
        context.call(cmd)
    except CalledProcessError:
        api.current_logger().error('Cannot set the dnf configuration')
        raise
    api.current_logger().debug('The DNF configuration has been updated to exclude leapp packages.')


def exclude_leapp_rpms(context, disable_plugins):
    """
    Ensure the leapp RPMs are excluded from any DNF transaction.

    This has to be called several times to ensure that our RPMs are not removed
    or updated (replaced) during the IPU. The action should happen inside
        - the target userspace container
        - on the host system
    So user will have to drop these packages from the exclude after the
    upgrade.
    """
    to_exclude = list(set(_get_excluded_pkgs(context, disable_plugins) + get_leapp_packages()))
    _set_excluded_pkgs(context, to_exclude, disable_plugins)
