import logging
import pytest
import os
import ldap
import time
from lib389._constants import *
from lib389.topologies import create_topology
from lib389._constants import *
from lib389 import Entry

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__)

CHANGELOG = 'cn=changelog5,cn=config'
MAXAGE_ATTR = 'nsslapd-changelogmaxage'
MAXAGE_INT = 60
MAXAGE_VALUE = str(MAXAGE_INT)
TRIMINTERVAL_INT = 10
TRIMINTERVAL = 'nsslapd-changelogtrim-interval'
MAX_USERS = 10
PEOPLE_DN = ("ou=people," + DEFAULT_SUFFIX)

def _check_entry_exist(master, dn, loops=10, wait=1):
    attempt = 0
    while attempt <= loops:
        try:
            ent = master.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
            break
        except ldap.NO_SUCH_OBJECT:
            attempt = attempt + 1
            time.sleep(wait)
        except ldap.LDAPError as e:
            log.fatal('Failed to retrieve user (%s): error %s' % (dn, e.message['desc']))
            assert False
    assert attempt <= loops

@pytest.fixture(scope="module")
def topo(request):
    """Create a topology with 2 masters 1 consumers"""

    topology = create_topology({
        ReplicaRole.MASTER: 2,
        ReplicaRole.CONSUMER: 1,
        })
    # You can write replica test here. Just uncomment the block and choose instances
    # replicas = Replicas(topology.ms["master1"])
    # replicas.test(DEFAULT_SUFFIX, topology.cs["consumer1"])

    def fin():
        """If we are debugging just stop the instances, otherwise remove them"""

        if DEBUGGING:
            map(lambda inst: inst.stop(), topology.all_insts.values())
        else:
            map(lambda inst: inst.delete(), topology.all_insts.values())

    request.addfinalizer(fin)

    return topology


def test_ticket49413(topo):
    """Specify a test case purpose or name here

    :id: 318ab69e-b560-4066-ad59-75ebfab739db
    :setup: Fill in set up configuration here
    :steps:
        1. Configure changelog trimming (maxage=60 and interval=10)
        2. Creates updates on M1 and M2 (that will be also replicated to C1)
        3. wait for maxage so any previous updates is older than maxage
        4. Pause (disable) the replica agreement M1->M2
        5. Do updates on M1, and add a TEST_ENTRY on M1
        6. wait until we are sure trimming thread have completed
        7. Resume (enable) the replica agreement M1->M2
        8. Check that TEST_ENTRY is present on M1, M2 and C1
    :expectedresults:
        1. Fill in the result that is expected
        2. For each test step
    """
    M1 = topo.ms["master1"]
    M2 = topo.ms["master2"]
    C1 = topo.cs["consumer1"]

    
    # Step 1
    M1.modify_s(CHANGELOG, [(ldap.MOD_REPLACE, MAXAGE_ATTR, MAXAGE_VALUE),
                            (ldap.MOD_REPLACE, TRIMINTERVAL, str(TRIMINTERVAL_INT))])
    M2.modify_s(CHANGELOG, [(ldap.MOD_REPLACE, MAXAGE_ATTR, MAXAGE_VALUE),
                            (ldap.MOD_REPLACE, TRIMINTERVAL, str(TRIMINTERVAL_INT))])
    
    # Step 2
    for idx in range(1, MAX_USERS):
        try:
            USER_DN = ("uid=user_%d,%s" % (idx, PEOPLE_DN))
            M1.add_s(Entry((USER_DN,
                                    {'objectclass': 'top extensibleObject'.split(),
                                     'uid': 'user_%d' % (idx)})))
        except ldap.LDAPError as e:
            log.fatal('Failed to add user (%s): error %s' % (USER_DN, e.message['desc']))
            assert False
    
    time.sleep(5)
    for idx in range(1, MAX_USERS):
        try:
            USER_DN = ("uid=user_%d,%s" % (idx, PEOPLE_DN))
            M2.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'description', 'value from M2')])
        except ldap.LDAPError as e:
            log.fatal('Failed to update user (%s): error %s' % (USER_DN, e.message['desc']))
            assert False
    
    # Step 3
    time.sleep(MAXAGE_INT)
    
    # Step 4
    agreement_m1_m2 = M1.agreement.list(suffix=SUFFIX, consumer_host=M2.host, consumer_port=M2.port)
    M1.agreement.pause(agreement_m1_m2[0].dn)
    
    # Step 5
    for idx in range(1, MAX_USERS):
        try:
            USER_DN = ("uid=user_%d,%s" % (idx, PEOPLE_DN))
            M1.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'description', 'value from M1')])
        except ldap.LDAPError as e:
            log.fatal('Failed to update user (%s): error %s' % (USER_DN, e.message['desc']))
            assert False
    TEST_ENTRY_DN = "uid=last_user,%s" % PEOPLE_DN
    M1.add_s(Entry((TEST_ENTRY_DN,
                            {'objectclass': 'top extensibleObject'.split(),
                             'uid': 'last_user'})))
    # Step 6
    time.sleep(TRIMINTERVAL_INT + 10)
    
    # Step 7
    M1.agreement.resume(agreement_m1_m2[0].dn)
    
    # Step 8
    _check_entry_exist(M1, TEST_ENTRY_DN)
    _check_entry_exist(C1, TEST_ENTRY_DN)
    _check_entry_exist(M2, TEST_ENTRY_DN)

            
    # If you need any test suite initialization,
    # please, write additional fixture for that (including finalizer).
    # Topology for suites are predefined in lib389/topologies.py.

    # If you need host, port or any other data about instance,
    # Please, use the instance object attributes for that (for example, topo.ms["master1"].serverid)

    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)

