# coding=utf-8
#
# Copyright (C) 2009-2021 Splunk Inc. All Rights Reserved.

from __future__ import absolute_import, division, print_function, unicode_literals

from collections import OrderedDict

import app
import ldap3
from .six import itervalues
from .six.moves import filter


class ConnectionPool(object):
    """
    Represents the set of domains defined in ldap.conf as a set of LDAP Connection objects.

    This class supports domain name selection by the ldapfetch, ldapfilter, and ldapgroup commands. These commands
    :py:meth:`~ConnectionPool.select` a domain to query for each input event record they process. Connections are
    instantiated and opened on first use and closed on :py:meth:`~ConnectionPool.__exit__`.

    """
    def __init__(self, configuration, attributes):
        self.configuration = configuration
        self.connections = OrderedDict()
        self.attributes = attributes

    def __enter__(self):
        self.attributes = app.get_normalized_attribute_names(self.attributes, self.select('default'), self.configuration)
        return self

    def __exit__(self, exception_type, exception, traceback):
        for connection in filter(None, itervalues(self.connections)):
            try:
                connection.unbind()
            except Exception as error:
                # Socket is sometimes closed before the connection.strategy.close method is called by connection.unbind
                # Further, for reasons yet to be ascertained, the error caught by Python at this site is NOT recognized
                # for what it is: an ldap3.LDAPSocketCloseError. Hence, we catch any exception here and--for now--
                # hope for the best.
                # TODO: Get a fix to this issue when we next update ldap3 or pin it on the Python runtime and let it be
                self.configuration.command.logger.debug('Swallowed exception of type %s: %s', type(error), error)
                pass

        self.configuration.command.logger.debug('Re-raise exception type: %s', exception_type)
        return exception_type is None  # meaning: do not swallow, but re-raise any exception presented by the runtime

    def select(self, domain):

        connection = self.connections.get(domain)

        if connection is None:

            configuration = self.configuration
            try:
                configuration.select(domain)
            except KeyError:
                self.connections[domain] = connection = None
            else:
                connection = ldap3.Connection(
                    configuration.server,
                    read_only=True,
                    raise_exceptions=True,
                    user=configuration.credentials.username,
                    password=configuration.credentials.password)

                connection.bind()
                self.connections[domain] = connection

        return connection
