import time
import ldap
import logging
import pytest
from lib389 import DirSrv, Entry, tools, tasks
from lib389.tools import DirSrvTools
from lib389._constants import *
from lib389.properties import *
from lib389.tasks import *
from lib389.utils import *
from lib389.topologies import topology_st as topo

DEBUGGING = os.getenv("DEBUGGING", default=False)
if DEBUGGING:
    logging.getLogger(__name__).setLevel(logging.DEBUG)
else:
    logging.getLogger(__name__).setLevel(logging.INFO)
log = logging.getLogger(__name__)

MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
GROUP_1_LVL_1_DN = ("cn=group_1_lvl_1," + DEFAULT_SUFFIX)
GROUP_2_LVL_1_DN = ("cn=group_2_lvl_1," + DEFAULT_SUFFIX)
GROUP_LVL_2_DN = ("cn=group_lvl_2," + DEFAULT_SUFFIX)
USER_DN = ("uid=a_user,%s" % (DEFAULT_SUFFIX))

def pattern_errorlog(file, log_pattern):
    try:
        pattern_errorlog.last_pos += 1
    except AttributeError:
        pattern_errorlog.last_pos = 0

    found = None
    log.debug("_pattern_errorlog: start at offset %d" % pattern_errorlog.last_pos)
    file.seek(pattern_errorlog.last_pos)

    # Use a while true iteration because 'for line in file: hit a
    # python bug that break file.tell()
    while True:
        line = file.readline()
        log.debug("_pattern_errorlog: [%d] %s" % (file.tell(), line))
        found = log_pattern.search(line)
        if ((line == '') or (found)):
            break

    log.debug("_pattern_errorlog: end at offset %d" % file.tell())
    pattern_errorlog.last_pos = file.tell()
    return found

def _find_memberof(topo, user_dn=None, group_dn=None, find_result=True):
    ent = topo.standalone.getEntry(user_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
    found = False
    if ent.hasAttr('memberof'):

        for val in ent.getValues('memberof'):
            topo.standalone.log.info("!!!!!!! %s: memberof->%s" % (user_dn, val))
            if val == group_dn:
                found = True
                break

    if find_result:
        assert (found)
    else:
        assert (not found)
        
def test_ticket49265(topo):
    """Write your testcase here...

    Also, if you need any testcase initialization,
    please, write additional fixture for that(include finalizer).
    
     - enable memberof
     - create group_1_lvl_1, group_2_lvl_1 and group_lvl_2
     - create a user
     - make group_1_lvl_1 memberof group_lvl_2
     - make user as member of group_lvl_2
     - check there is no 'memberof_fix_memberof_callback: Weird,' message
     - make user as member of group_1_lvl_1
     - check there is no 'memberof_fix_memberof_callback: Weird,' message
     
    """

    topo.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
    # Configure memberOf group attribute
    try:
        topo.standalone.modify_s(MEMBEROF_PLUGIN_DN,
                                        [(ldap.MOD_REPLACE,
                                          'memberofgroupattr',
                                          'member')])
    except ldap.LDAPError as e:
        log.fatal('Failed to configure memberOf plugin: error ' + e.message['desc'])
        assert False
    topo.standalone.restart()
    topo.standalone.errorlog_file = open(topo.standalone.errlog, "r")
    
    # Create groups
    try:
        topo.standalone.add_s(Entry((GROUP_1_LVL_1_DN,
                                            {'objectclass': 'top groupofnames'.split(),
                                             'cn': 'group_1_lvl_1'})))
    except ldap.LDAPError as e:
        log.fatal('Failed to add group: error ' + e.message['desc'])
        assert False
    try:
        topo.standalone.add_s(Entry((GROUP_LVL_2_DN,
                                            {'objectclass': 'top groupofnames'.split(),
                                             'cn': 'group_lvl_2'})))
    except ldap.LDAPError as e:
        log.fatal('Failed to add group: error ' + e.message['desc'])
        assert False
    try:
        topo.standalone.add_s(Entry((GROUP_2_LVL_1_DN,
                                            {'objectclass': 'top groupofnames'.split(),
                                             'cn': 'group_2_lvl_1'})))
    except ldap.LDAPError as e:
        log.fatal('Failed to add group: error ' + e.message['desc'])
        assert False
    
    # Create a user
    try:
        
        topo.standalone.add_s(Entry((USER_DN,
                                            {'objectclass': 'top extensibleObject'.split(),
                                             'uid': 'a_user' })))
    except ldap.LDAPError as e:
        log.fatal('Failed to add user (%s): error %s' % (USER_DN, e.message['desc']))
        assert False
    
    # Make group_1_lvl_1 memberof group_lvl_2
    try:
        topo.standalone.modify_s(GROUP_LVL_2_DN,
                                            [(ldap.MOD_ADD,
                                              'member',
                                              GROUP_1_LVL_1_DN)])
    except ldap.LDAPError as e:
            log.fatal('Failed to update group: member (%s) - error: %s' %
                      (GROUP_1_LVL_1_DN, e.message['desc']))
    _find_memberof(topo, user_dn=GROUP_1_LVL_1_DN, group_dn=GROUP_LVL_2_DN, find_result=True)
    
    # make USER_DN memberof GROUP_LVL_2_DN 
    try:
        topo.standalone.modify_s(GROUP_LVL_2_DN,
                                            [(ldap.MOD_ADD,
                                              'member',
                                              USER_DN)])
    except ldap.LDAPError as e:
            log.fatal('Failed to update group lvl 2: member (%s) - error: %s' %
                      (USER_DN, e.message['desc']))
    # Check the error log does not contain an error.
    # It should not happen even if the bug is not fixed
    regex = re.compile("memberof_fix_memberof_callback: Weird")
    assert not pattern_errorlog(topo.standalone.errorlog_file, regex)
    
    # make USER_DN memberof GROUP_1_LVL_1_DN (while it is also member of GROUP_LVL_2_DN)
    try:
        topo.standalone.modify_s(GROUP_1_LVL_1_DN,
                                            [(ldap.MOD_ADD,
                                              'member',
                                              USER_DN)])
    except ldap.LDAPError as e:
            log.fatal('Failed to update group 1 lvl 1: member (%s) - error: %s' %
                      (USER_DN, e.message['desc']))
    # Check the error log does not contain an error.
    # It fails if the bug is not fixed if the bug is not fixed
    assert not pattern_errorlog(topo.standalone.errorlog_file, regex)
    
    # make USER_DN memberof GROUP_2_LVL_1_DN (while it is also member of GROUP_1_LVL_1_DN and GROUP_LVL_2_DN)
    try:
        topo.standalone.modify_s(GROUP_2_LVL_1_DN,
                                            [(ldap.MOD_ADD,
                                              'member',
                                              USER_DN)])
    except ldap.LDAPError as e:
            log.fatal('Failed to update group 2 lvl 1: member (%s) - error: %s' %
                      (USER_DN, e.message['desc']))
    # Check the error log does not contain an error.
    assert not pattern_errorlog(topo.standalone.errorlog_file, regex)
    
    # additional controls
    _find_memberof(topo, user_dn=USER_DN, group_dn=GROUP_LVL_2_DN, find_result=True)
    _find_memberof(topo, user_dn=USER_DN, group_dn=GROUP_1_LVL_1_DN, find_result=True)
    _find_memberof(topo, user_dn=USER_DN, group_dn=GROUP_2_LVL_1_DN, find_result=True)
    _find_memberof(topo, user_dn=GROUP_1_LVL_1_DN, group_dn=GROUP_LVL_2_DN, find_result=True)
    
    if DEBUGGING:
        # Add debugging steps(if any)...
        pass


if __name__ == '__main__':
    # Run isolated
    # -s for DEBUG mode
    CURRENT_FILE = os.path.realpath(__file__)
    pytest.main("-s %s" % CURRENT_FILE)

