#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

""" This is the LDAP component of PyPubSub """

import ldap
import asyncio


class LDAPConnection:
    def __init__(self, yml: dict):
        self.uri = yml.get('uri')
        assert isinstance(self.uri, str), "LDAP URI must be a string."

        self.user = yml.get('user_dn')
        assert isinstance(self.user, str) or self.user is None, "LDAP user DN must be a string or absent."

        self.base = yml.get('base_scope')
        assert isinstance(self.base, str), "LDAP Base scope must be a string"

        self.patterns: list = yml.get('membership_patterns', [])
        assert isinstance(self.patterns, list), "LDAP membership patterns must be a list of pattern strings"

        self.acl = yml.get('acl')
        assert isinstance(self.acl, dict), "LDAP ACL must be a dictionary (hash) of ACLs"

        assert ldap.initialize(self.uri)
        print("==== LDAP configuration looks kosher, enabling LDAP authentication as fallback ====")

    async def get_groups(self, user: str, password: str):
        """Async fetching of groups an LDAP user belongs to"""
        bind_dn = self.user % user  # Interpolate user DN with username

        try:
            client = ldap.initialize(self.uri)
            client.set_option(ldap.OPT_REFERRALS, 0)
            client.set_option(ldap.OPT_TIMEOUT, 0)
            rv = client.simple_bind(bind_dn, password)
            while True:
                res = client.result(rv, timeout=0)
                if res and res != (None, None):
                    break
                await asyncio.sleep(0.25)

            groups = []
            for role in self.patterns:
                rv = client.search(self.base, ldap.SCOPE_SUBTREE, role % user, ['dn'])
                while True:
                    res = client.result(rv, all=0, timeout=0)
                    if res:
                        if res == (None, None):
                            await asyncio.sleep(0.25)
                        else:
                            if not res[1]:
                                break
                            for tuples in res[1]:
                                groups.append(tuples[0])
                    else:
                        break
            return groups

        except Exception as e:
            print(f"LDAP Exception for user {user}: {e}")
            return []