"""
Management of PostgreSQL groups (roles)
=======================================

The postgres_group module is used to create and manage Postgres groups.

.. code-block:: yaml

    frank:
      postgres_group.present
"""

import logging

# Salt imports
from salt.modules import postgres

log = logging.getLogger(__name__)


def __virtual__():
    """
    Only load if the postgres module is present
    """
    if "postgres.group_create" not in __salt__:
        return (
            False,
            "Unable to load postgres module.  Make sure `postgres.bins_dir` is set.",
        )
    return True


def present(
    name,
    createdb=None,
    createroles=None,
    encrypted=None,
    superuser=None,
    inherit=None,
    login=None,
    replication=None,
    password=None,
    refresh_password=None,
    groups=None,
    user=None,
    maintenance_db=None,
    db_password=None,
    db_host=None,
    db_port=None,
    db_user=None,
):
    """
    Ensure that the named group is present with the specified privileges
    Please note that the user/group notion in postgresql is just abstract, we
    have roles, where users can be seen as roles with the ``LOGIN`` privilege
    and groups the others.

    name
        The name of the group to manage

    createdb
        Is the group allowed to create databases?

    createroles
        Is the group allowed to create other roles/users

    encrypted
        How the password should be stored.

        If encrypted is ``None``, ``True``, or ``md5``, it will use
        PostgreSQL's MD5 algorithm.

        If encrypted is ``False``, it will be stored in plaintext.

        If encrypted is ``scram-sha-256``, it will use the algorithm described
        in RFC 7677.

        .. versionchanged:: 3003

            Prior versions only supported ``True`` and ``False``

    login
        Should the group have login perm

    inherit
        Should the group inherit permissions

    superuser
        Should the new group be a "superuser"

    replication
        Should the new group be allowed to initiate streaming replication

    password
        The group's password.
        It can be either a plain string or a pre-hashed password::

            'md5{MD5OF({password}{role}}'
            'SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}'

        If encrypted is not ``False``, then the password will be converted
        to the appropriate format above, if not already. As a consequence,
        passwords that start with "md5" or "SCRAM-SHA-256" cannot be used.

    refresh_password
        Password refresh flag

        Boolean attribute to specify whether to password comparison check
        should be performed.

        If refresh_password is ``True``, the password will be automatically
        updated without extra password change check.

        This behaviour makes it possible to execute in environments without
        superuser access available, e.g. Amazon RDS for PostgreSQL

    groups
        A string of comma separated groups the group should be in

    user
        System user all operations should be performed on behalf of

        .. versionadded:: 0.17.0

    db_user
        database username if different from config or default

    db_password
        user password if any password for a specified user

    db_host
        Database host if different from config or default

    db_port
        Database port if different from config or default
    """
    ret = {
        "name": name,
        "changes": {},
        "result": True,
        "comment": f"Group {name} is already present",
    }

    # default to encrypted passwords
    if encrypted is None:
        encrypted = postgres._DEFAULT_PASSWORDS_ENCRYPTION

    db_args = {
        "maintenance_db": maintenance_db,
        "runas": user,
        "host": db_host,
        "user": db_user,
        "port": db_port,
        "password": db_password,
    }

    # check if group exists
    mode = "create"
    group_attr = __salt__["postgres.role_get"](
        name, return_password=not refresh_password, **db_args
    )
    if group_attr is not None:
        mode = "update"

    if password is not None:
        if (
            mode == "update"
            and not refresh_password
            and postgres._verify_password(
                name, password, group_attr["password"], encrypted
            )
        ):
            # if password already matches then don't touch it
            password = None
        else:
            # encrypt password if necessary
            password = postgres._maybe_encrypt_password(
                name, password, encrypted=encrypted
            )

    # The user is not present, make it!
    update = {}
    if mode == "update":
        role_groups = group_attr.get("groups", [])
        if createdb is not None and group_attr["can create databases"] != createdb:
            update["createdb"] = createdb
        if inherit is not None and group_attr["inherits privileges"] != inherit:
            update["inherit"] = inherit
        if login is not None and group_attr["can login"] != login:
            update["login"] = login
        if createroles is not None and group_attr["can create roles"] != createroles:
            update["createroles"] = createroles
        if replication is not None and group_attr["replication"] != replication:
            update["replication"] = replication
        if superuser is not None and group_attr["superuser"] != superuser:
            update["superuser"] = superuser
        if password is not None:
            update["password"] = True
        if groups is not None:
            lgroups = groups
            if isinstance(groups, str):
                lgroups = lgroups.split(",")
            if isinstance(lgroups, list):
                missing_groups = [a for a in lgroups if a not in role_groups]
                if missing_groups:
                    update["groups"] = missing_groups

    if mode == "create" or (mode == "update" and update):
        if __opts__["test"]:
            if update:
                ret["changes"][name] = update
            ret["result"] = None
            ret["comment"] = f"Group {name} is set to be {mode}d"
            return ret
        cret = __salt__[f"postgres.group_{mode}"](
            groupname=name,
            createdb=createdb,
            createroles=createroles,
            encrypted=encrypted,
            login=login,
            inherit=inherit,
            superuser=superuser,
            replication=replication,
            rolepassword=password,
            groups=groups,
            **db_args,
        )
    else:
        cret = None

    if cret:
        ret["comment"] = f"The group {name} has been {mode}d"
        if update:
            ret["changes"][name] = update
        else:
            ret["changes"][name] = "Present"
    elif cret is not None:
        ret["comment"] = f"Failed to {mode} group {name}"
        ret["result"] = False
    else:
        ret["result"] = True

    return ret


def absent(
    name,
    user=None,
    maintenance_db=None,
    db_password=None,
    db_host=None,
    db_port=None,
    db_user=None,
):
    """
    Ensure that the named group is absent

    name
        The groupname of the group to remove

    user
        System user all operations should be performed on behalf of

        .. versionadded:: 0.17.0

    db_user
        database username if different from config or default

    db_password
        user password if any password for a specified user

    db_host
        Database host if different from config or default

    db_port
        Database port if different from config or default
    """
    ret = {"name": name, "changes": {}, "result": True, "comment": ""}

    db_args = {
        "maintenance_db": maintenance_db,
        "runas": user,
        "host": db_host,
        "user": db_user,
        "port": db_port,
        "password": db_password,
    }
    # check if group exists and remove it
    if __salt__["postgres.user_exists"](name, **db_args):
        if __opts__["test"]:
            ret["result"] = None
            ret["comment"] = f"Group {name} is set to be removed"
            return ret
        if __salt__["postgres.group_remove"](name, **db_args):
            ret["comment"] = f"Group {name} has been removed"
            ret["changes"][name] = "Absent"
            return ret
        else:
            ret["result"] = False
            ret["comment"] = f"Group {name} failed to be removed"
            return ret
    else:
        ret["comment"] = f"Group {name} is not present, so it cannot be removed"

    return ret
