"""
Package support for AIX

.. important::
    If you feel that Salt should be using this module to manage filesets or
    rpm packages on a minion, and it is using a different module (or gives an
    error similar to *'pkg.install' is not available*), see :ref:`here
    <module-provider-override>`.
"""

import copy
import logging
import os

import salt.utils.data
import salt.utils.functools
import salt.utils.path
import salt.utils.pkg
from salt.exceptions import CommandExecutionError

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = "pkg"


def __virtual__():
    """
    Set the virtual pkg module if the os is AIX
    """
    if __grains__["os_family"] == "AIX":
        return __virtualname__
    return (False, "Did not load AIX module on non-AIX OS.")


def _check_pkg(target):
    """
    Return name, version and if rpm package for specified target
    """
    ret = {}
    cmd = ["/usr/bin/lslpp", "-Lc", target]
    lines = __salt__["cmd.run"](cmd, python_shell=False).splitlines()

    name = ""
    version_num = ""
    rpmpkg = False
    for line in lines:
        if line.startswith("#"):
            continue

        comps = line.split(":")
        if len(comps) < 7:
            raise CommandExecutionError(
                "Error occurred finding fileset/package",
                info={"errors": comps[1].strip()},
            )

        # handle first matching line
        if "R" in comps[6]:
            name = comps[0]
            rpmpkg = True
        else:
            name = comps[1]  # use fileset rather than rpm package

        version_num = comps[2]
        break

    return name, version_num, rpmpkg


def _is_installed_rpm(name):
    """
    Returns True if the rpm package is installed. Otherwise returns False.
    """
    cmd = ["/usr/bin/rpm", "-q", name]
    return __salt__["cmd.retcode"](cmd) == 0


def _list_pkgs_from_context(versions_as_list):
    """
    Use pkg list from __context__
    """
    if versions_as_list:
        return __context__["pkg.list_pkgs"]
    else:
        ret = copy.deepcopy(__context__["pkg.list_pkgs"])
        __salt__["pkg_resource.stringify"](ret)
        return ret


def list_pkgs(versions_as_list=False, **kwargs):
    """
    List the filesets/rpm packages currently installed as a dict:

    .. code-block:: python

        {'<package_name>': '<version>'}

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_pkgs
    """
    ret = {}
    versions_as_list = salt.utils.data.is_true(versions_as_list)
    # not yet implemented or not applicable
    if any(
        [salt.utils.data.is_true(kwargs.get(x)) for x in ("removed", "purge_desired")]
    ):
        return ret

    if "pkg.list_pkgs" in __context__ and kwargs.get("use_context", True):
        return _list_pkgs_from_context(versions_as_list)

    # cmd returns information colon delimited in a single linei, format
    #   Package Name:Fileset:Level:State:PTF Id:Fix State:Type:Description:
    #       Destination Dir.:Uninstaller:Message Catalog:Message Set:
    #       Message Number:Parent:Automatic:EFIX Locked:Install Path:Build Date
    # Example:
    #   xcursor:xcursor-1.1.7-3:1.1.7-3: : :C:R:X Cursor library: :\
    #       /bin/rpm -e xcursor: : : : :0: :(none):Mon May  8 15:18:35 CDT 2017
    #   bos:bos.rte.libcur:7.1.5.0: : :C:F:libcurses Library: : : : : : :0:0:/:1731
    #
    # where Type codes: F -- Installp Fileset, P -- Product, C -- Component,
    #                   T -- Feature, R -- RPM Package
    cmd = "/usr/bin/lslpp -Lc"
    lines = __salt__["cmd.run"](cmd, python_shell=False).splitlines()

    for line in lines:
        if line.startswith("#"):
            continue

        comps = line.split(":")
        if len(comps) < 7:
            continue

        if "R" in comps[6]:
            name = comps[0]
        else:
            name = comps[1]  # use fileset rather than rpm package

        version_num = comps[2]
        __salt__["pkg_resource.add_pkg"](ret, name, version_num)

    __salt__["pkg_resource.sort_pkglist"](ret)
    __context__["pkg.list_pkgs"] = copy.deepcopy(ret)

    if not versions_as_list:
        __salt__["pkg_resource.stringify"](ret)

    return ret


def version(*names, **kwargs):
    """
    Common interface for obtaining the version of installed fileset/rpm package.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.version vim
        salt '*' pkg.version foo bar baz
    """
    return __salt__["pkg_resource.version"](*names, **kwargs)


def _is_installed(name, **kwargs):
    """
    Returns True if the fileset/rpm package is installed. Otherwise returns False.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg._is_installed bash
    """
    cmd = ["/usr/bin/lslpp", "-Lc", name]
    return __salt__["cmd.retcode"](cmd) == 0


def install(name=None, refresh=False, pkgs=None, version=None, test=False, **kwargs):
    """
    Install the named fileset(s)/rpm package(s).

    name
        The name of the fileset or rpm package to be installed.

    refresh
        Whether or not to update the yum database before executing.


    Multiple Package Installation Options:

    pkgs
        A list of filesets and/or rpm packages to install.
        Must be passed as a python list. The ``name`` parameter will be
        ignored if this option is passed.

    version
        Install a specific version of a fileset/rpm package.
        (Unused at present).

    test
        Verify that command functions correctly:

    Returns a dict containing the new fileset(s)/rpm package(s) names and versions:

        {'<package>': {'old': '<old-version>',
                       'new': '<new-version>'}}

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.install /stage/middleware/AIX/bash-4.2-3.aix6.1.ppc.rpm
        salt '*' pkg.install /stage/middleware/AIX/bash-4.2-3.aix6.1.ppc.rpm refresh=True
        salt '*' pkg.install /stage/middleware/AIX/VIOS2211_update/tpc_4.1.1.85.bff
        salt '*' pkg.install /stage/middleware/AIX/Xlc/usr/sys/inst.images/xlC.rte
        salt '*' pkg.install /stage/middleware/AIX/Firefox/ppc-AIX53/Firefox.base
        salt '*' pkg.install pkgs='["foo", "bar"]'
    """
    targets = salt.utils.args.split_input(pkgs) if pkgs else [name]
    if not targets:
        return {}

    if pkgs:
        log.debug("Removing these fileset(s)/rpm package(s) %s: %s", name, targets)

    # Get a list of the currently installed pkgs.
    old = list_pkgs()

    # Install the fileset or rpm package(s)
    errors = []
    for target in targets:
        filename = os.path.basename(target)
        if filename.endswith(".rpm"):
            if _is_installed_rpm(filename.split(".aix")[0]):
                continue

            cmdflags = " -Uivh "
            if test:
                cmdflags += " --test"

            cmd = ["/usr/bin/rpm", cmdflags, target]
            out = __salt__["cmd.run_all"](cmd, output_loglevel="trace")
        else:
            if _is_installed(target):
                continue

            cmd = "/usr/sbin/installp -acYXg"
            if test:
                cmd += "p"
            cmd += " -d "
            dirpath = os.path.dirname(target)
            cmd += dirpath + " " + filename
            out = __salt__["cmd.run_all"](cmd, output_loglevel="trace")

        if 0 != out["retcode"]:
            errors.append(out["stderr"])

    # Get a list of the packages after the uninstall
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if errors:
        raise CommandExecutionError(
            "Problems encountered installing filesets(s)/package(s)",
            info={"changes": ret, "errors": errors},
        )

    # No error occurred
    if test:
        return "Test succeeded."

    return ret


def remove(name=None, pkgs=None, **kwargs):
    """
    Remove specified fileset(s)/rpm package(s).

    name
        The name of the fileset or rpm package to be deleted.


    Multiple Package Options:

    pkgs
        A list of filesets and/or rpm packages to delete.
        Must be passed as a python list. The ``name`` parameter will be
        ignored if this option is passed.


    Returns a list containing the removed packages.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.remove <fileset/rpm package name>
        salt '*' pkg.remove tcsh
        salt '*' pkg.remove xlC.rte
        salt '*' pkg.remove Firefox.base.adt
        salt '*' pkg.remove pkgs='["foo", "bar"]'
    """
    targets = salt.utils.args.split_input(pkgs) if pkgs else [name]
    if not targets:
        return {}

    if pkgs:
        log.debug("Removing these fileset(s)/rpm package(s) %s: %s", name, targets)

    errors = []

    # Get a list of the currently installed pkgs.
    old = list_pkgs()

    # Remove the fileset or rpm package(s)
    for target in targets:
        try:
            named, versionpkg, rpmpkg = _check_pkg(target)
        except CommandExecutionError as exc:
            if exc.info:
                errors.append(exc.info["errors"])
            continue

        if rpmpkg:
            cmd = ["/usr/bin/rpm", "-e", named]
            out = __salt__["cmd.run_all"](cmd, output_loglevel="trace")
        else:
            cmd = ["/usr/sbin/installp", "-u", named]
            out = __salt__["cmd.run_all"](cmd, output_loglevel="trace")

    # Get a list of the packages after the uninstall
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if errors:
        raise CommandExecutionError(
            "Problems encountered removing filesets(s)/package(s)",
            info={"changes": ret, "errors": errors},
        )

    return ret


def latest_version(*names, **kwargs):
    """
    Return the latest version of the named fileset/rpm package available for
    upgrade or installation. If more than one fileset/rpm package name is
    specified, a dict of name/version pairs is returned.

    If the latest version of a given fileset/rpm package is already installed,
    an empty string will be returned for that package.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.latest_version <package name>
        salt '*' pkg.latest_version <package1> <package2> <package3> ...

    NOTE: Repositories are not presently supported for AIX.
    This function will always return an empty string for a given
    fileset/rpm package.
    """
    kwargs.pop("refresh", True)

    ret = {}
    if not names:
        return ""
    for name in names:
        ret[name] = ""

    # Return a string if only one package name passed
    if len(names) == 1:
        return ret[names[0]]
    return ret


# available_version is being deprecated
available_version = salt.utils.functools.alias_function(
    latest_version, "available_version"
)


def upgrade_available(name, **kwargs):
    """
    Check whether or not an upgrade is available for a given package

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.upgrade_available <package name>
    """
    return latest_version(name) != ""
