"""RabbitMQSynchronizer

  RabbitMQSynchronizer keeps the RabbitMQ user database  synchronized with the CS
  RabbitMQ user database is updated according to changes in CS.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from DIRAC import S_OK
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import hostHasProperties
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getDNsInGroup
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getDNForHost
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getHosts
from DIRAC.Core.Utilities.RabbitMQAdmin import getAllUsers, deleteUsers
from DIRAC.Core.Utilities.RabbitMQAdmin import setUsersPermissions, addUsersWithoutPasswords
from DIRAC.ResourceStatusSystem.Utilities import CSHelpers
from DIRAC import gLogger
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations  # for getValidPilotGroupName()


class RabbitMQSynchronizer(object):
    def __init__(self):

        # Warm up local CS
        # I am not sure whether it is needed but
        # it was used in DIRAC.ResourceStatusSystem.Utilities.Synchronizer
        CSHelpers.warmUp()
        # only users belonging to group with this property are allowed to connect
        self._accessUserGroup = getAllowedGroupName()
        self._accessProperty = getAllowedHostProperty()  # only host with this property are allowed to connect

    def sync(self, _eventName, _params):
        """Synchronizes the internal RabbitMQ user database with the current content of CS.

        Args:
          _eventName: any value, this parameter is ignored, but needed by caller function.
          _params: - any value, this parameter is ignored, but needed by caller function.
        Returns:
          S_OK:
        Example:
           s.sync( None, None )
        """

        valid_users = getDNsInGroup(self._accessUserGroup)
        valid_hosts = getDNsForValidHosts(self._accessProperty)
        updateRabbitMQDatabase(valid_users + valid_hosts)
        return S_OK()


def getAllowedGroupName():
    """Returns name of the group of which users are allowed
       to connect to RabbitMQ server.

    Returns:
      str: group name
    """
    return Operations().getValue("Pilot/GenericPilotGroup", "")


def getAllowedHostProperty():
    """Returns property. The hosts that contain it
       are allowed to connect to RabbitMQ server.

    Returns:
      str: property name
    """
    # It should be taken from CS
    return "GenericPilot"


def getDNsForValidHosts(accessProperty):
    """Returns DN of hosts which contains accessProperty based on current
       CS settings.

    Args:
      accessProperty(str):

    Returns:
      list of hosts with accessProperty set.

    :rtype: python:list

    """

    retVal = getHosts()
    if not retVal["OK"]:
        return []
    hosts = retVal["Value"]
    DNs = []
    for host in hosts:
        if hostHasProperties(host, [accessProperty]):
            retVal = getDNForHost(host)
            if retVal["OK"]:
                DNs.extend(retVal["Value"])
            else:
                gLogger.error("Could not find a correct DN for host: %s. It will be ignored." % host)
    return DNs


def updateRabbitMQDatabase(newUsers, specialUsers=None):
    """Updates the internal user database of RabbitMQ server.
       The current user list from the database is compared to newUsers list.
       The users that are not present in the database, but are in newUsers list
       are added. The users that are present in the database, but are not in
       newUsers list are deleted. The specialUser list contains logins that
       will not be processed at all.

    Args:
      newUsers(list): user logins to be processed.
      specialUsers(list): special users that will not be processed.

    :type newUsers: python:list
    :type specialUsers: python:list

    """
    if specialUsers is None:
        # I think specialUsers should be read from CS and not taken as the argument
        # but I will leave it till we decide it.
        specialUsers = getSpecialUsersForRabbitMQDatabase()
    ret = getAllUsers()
    if not ret["OK"]:
        gLogger.error("Some problem with getting all users from RabbitMQ DB")
        return ret
    currentUsersInRabbitMQ = ret["Value"]
    # special users should not be taken into account
    currentUsersInRabbitMQ = listDifference(currentUsersInRabbitMQ, specialUsers)
    usersToRemove = listDifference(currentUsersInRabbitMQ, newUsers)
    usersToAdd = listDifference(newUsers, currentUsersInRabbitMQ)
    if usersToAdd:
        addUsersWithoutPasswords(usersToAdd)
        setUsersPermissions(usersToAdd)
    if usersToRemove:
        deleteUsers(usersToRemove)
    return S_OK()


def getSpecialUsersForRabbitMQDatabase():
    """Returns a list of special users
       that will not be processed (e.g. removed)
       while updating the RabbitMQ database.

    Returns:
      list of user logins.

    :rtype: python:list

    """
    # For a moment it is hardcoded but should be read from
    # some location in CS
    return ["admin", "dirac"]


def listDifference(list1, list2):
    """Calculates differences between two lists.
       The original order of the list is not preserved.

    :rtype: python:list
    """
    return list(set(list1) - set(list2))
