# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import os
import datetime
from django import db
from django.test import TestCase, Client, override_settings
from django.contrib.auth.models import User
# from django.contrib.auth.tests.custom_user import ExtensionUser
from coin.members.models import Member, MembershipFee, LdapUser, LdapGroup
import logging
import ldapdb
from pprint import pprint


class MemberTests(TestCase):

    def test_when_creating_member_a_ldapuser_is_also_created_with_same_data(self):
        """
        Test que lors de la création d'un nouveau membre, une entrée
        correspondante est bien créée dans le LDAP et qu'elle contient
        les mêmes données.
        Cela concerne le nom et le prénom
        """

        #~ Créé un membre
        first_name = 'Gérard'
        last_name = 'Majax'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name,
                        username=username)
        member.save()

        #~ Récupère l'utilisateur LDAP et fait les tests
        ldap_user = LdapUser.objects.get(pk=username)

        self.assertEqual(ldap_user.first_name, first_name)
        self.assertEqual(ldap_user.last_name, last_name)
        self.assertEqual(ldap_user.pk, username)

        member.delete()

    def test_when_modifiying_member_corresponding_ldap_user_is_also_modified_with_same_data(self):
        """
        Test que lorsque l'on modifie un membre, l'utilisateur LDAP
        correspondant est bien modifié
        Cela concerne le no met le prénom
        """
        #~ Créé un membre
        first_name = 'Ronald'
        last_name = 'Mac Donald'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)
        member.save()

        #~  Le modifie
        new_first_name = 'José'
        new_last_name = 'Bové'
        member.first_name = new_first_name
        member.last_name = new_last_name
        member.save()

        #~ Récupère l'utilisateur LDAP et fait les tests
        ldap_user = LdapUser.objects.get(pk=username)

        self.assertEqual(ldap_user.first_name, new_first_name)
        self.assertEqual(ldap_user.last_name, new_last_name)

        member.delete()

    # def test_when_creating_member_corresponding_ldap_user_is_in_coin_ldap_group(self):
    #     """
    #     Test que l'utilisateur Ldap fraichement créé est bien dans le group "coin"
    #     Et que lors de la supression d'un membre, l'utilisateur LDAP correspondant
    #     est bien retiré du groupe.
    #     """
    # ~ Créé un membre
    #     username = MemberTestsUtils.get_random_username()
    #     member = Member(first_name='Canard',
    #                     last_name='WC', username=username)
    #     member.save()

    # ~ Récupère le group "coin" et test que l'utilisateur y est présent
    #     ldap_group = LdapGroup.objects.get(pk="coin")
    #     self.assertEqual(username in ldap_group.members, True)

    # ~ Supprime l'utilisateur
    #     member.delete()

    # ~ Récupère le group "coin" et test que l'utilisateur n'y est plus
    #     ldap_group = LdapGroup.objects.get(pk="coin")
    #     self.assertEqual(username in ldap_group.members, False)

    #     LdapUser.objects.get(pk=username).delete()

    def test_change_password_and_auth(self):
        """
        Test que la fonction change_password de member fonctionne et permet
        l'authentification avec le nouveau mot de passe
        """
        username = MemberTestsUtils.get_random_username()
        password = "1234"

        #~ Créé un nouveau membre
        member = Member(first_name='Passe-partout',
                        last_name='Du fort Boyard', username=username)
        member.save()

        #~ Récupère l'utilisateur LDAP
        ldap_user = LdapUser.objects.get(pk=username)

        #~ Change son mot de passe
        member.set_password(password)
        member.save()

        #~ Test l'authentification django
        c = Client()
        self.assertEqual(c.login(username=username, password=password), True)

        # Test l'authentification ldap
        import ldap
        ldap_conn_settings = db.connections['ldap'].settings_dict
        l = ldap.initialize(ldap_conn_settings['NAME'])
        options = ldap_conn_settings.get('CONNECTION_OPTIONS', {})
        for opt, value in options.items():
            l.set_option(opt, value)

        if ldap_conn_settings.get('TLS', False):
            l.start_tls_s()

        # Raise "Invalid credentials" exception if auth fail
        l.simple_bind_s(ldap_conn_settings['USER'],
                        ldap_conn_settings['PASSWORD'])

        l.unbind_s()

        member.delete()

    def test_when_creating_member_ldap_display_name_is_well_defined(self):
        """
        Lors de la création d'un membre, le champ "display_name" du LDAP est
        prenom + nom
        """
        first_name = 'Gérard'
        last_name = 'Majax'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)
        member.save()

        #~ Récupère l'utilisateur LDAP
        ldap_user = LdapUser.objects.get(pk=username)

        self.assertEqual(ldap_user.display_name, '%s %s' %
                         (first_name, last_name))

        member.delete()

    def test_when_creating_member_username_is_well_defined(self):
        """
        Lors de la création d'un membre, le champ "username", s'il n'est pas
        définit doit être généré avec les contraintes suivantes :
        premières lettres du prénom + nom le tout en minuscule,
        sans caractères accentués et sans espaces.
        """
        random = os.urandom(4).encode('hex')
        first_name = 'Gérard-Étienne'
        last_name = 'Majax de la Boétie!B' + random

        control = 'gemajaxdelaboetieb' + random
        control = control[:30]

        member = Member(first_name=first_name, last_name=last_name)
        member.save()

        self.assertEqual(member.username, control)

        member.delete()

    def test_when_saving_member_and_ldap_fail_dont_save(self):
        """
        Test que lors de la sauvegarde d'un membre et que la sauvegarde en LDAP
        échoue (ici mauvais mot de passe), rien n'est sauvegardé en base
        """

        # Fait échouer le LDAP en définissant un mauvais mot de passe
        for dbconnection in db.connections.all():
            if (type(dbconnection) is
                    ldapdb.backends.ldap.base.DatabaseWrapper):
                dbconnection.settings_dict[
                    'PREVIOUSPASSWORD'] = dbconnection.settings_dict['PASSWORD']
                dbconnection.settings_dict['PASSWORD'] = 'wrong password test'

        # Créé un membre
        first_name = 'Du'
        last_name = 'Pont'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)

        # Le sauvegarde en base de donnée
        # Le save devrait renvoyer une exception parceque le LDAP échoue
        self.assertRaises(Exception, member.save)

        # On s'assure, malgré l'exception, que le membre n'est pas en base
        with self.assertRaises(Member.DoesNotExist):
            Member.objects.get(username=username)

        # Restaure le mot de passe pour les tests suivants
        for dbconnection in db.connections.all():
            if (type(dbconnection) is
                    ldapdb.backends.ldap.base.DatabaseWrapper):
                dbconnection.settings_dict[
                    'PASSWORD'] = dbconnection.settings_dict['PREVIOUSPASSWORD']

    # def test_when_user_login_member_user_field_is_updated(self):
    #     """
    #     Test que lorqu'un utilisateur se connect, le champ user du membre
    #     correspondant est mis à jour convenablement
    #     """
    # Créé un membre
    #     first_name = 'Du'
    #     last_name = 'Pond'
    #     password = '1234'
    #     username = MemberTestsUtils.get_random_username()
    #     member = Member(first_name=first_name,
    #                     last_name=last_name, username=username)
    #     member.save()
    #     member.change_password(password)

    # Vérifie que user non définit
    #     self.assertIsNone(member.user)

    # Connection
    #     c = Client()
    #     self.assertEqual(c.login(username=username, password=password), True)

    # Vérifie que user définit
    #     member = Member.objects.get(username=username)
    #     self.assertIsNotNone(member.user)

    #     LdapUser.objects.get(pk=member.username).delete()

    def test_member_end_date_of_memberhip(self):
        """
        Test que end_date_of_membership d'un membre envoi bien la date de fin d'adhésion
        """
        # Créer un membre
        first_name = 'Tin'
        last_name = 'Tin'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)
        member.save()

        start_date = datetime.date.today()
        end_date = start_date + datetime.timedelta(365)

        # Créé une cotisation
        membershipfee = MembershipFee(member=member, amount=20,
                                      start_date=start_date,
                                      end_date=end_date)
        membershipfee.save()

        self.assertEqual(member.end_date_of_membership(), end_date)

    def test_member_is_paid_up(self):
        """
        Test que end_date_of_membership d'un membre envoi bien la date de fin d'adhésion
        """
        # Créé un membre
        first_name = 'Capitain'
        last_name = 'Haddock'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)
        member.save()

        start_date = datetime.date.today()
        end_date = start_date + datetime.timedelta(365)

        # Test qu'un membre sans cotisation n'est pas à jour
        self.assertEqual(member.is_paid_up(), False)

        # Créé une cotisation passée
        membershipfee = MembershipFee(member=member, amount=20,
                                      start_date=datetime.date.today() -
                                      datetime.timedelta(365),
                                      end_date=datetime.date.today() - datetime.timedelta(10))
        membershipfee.save()
        # La cotisation s'étant terminée il y a 10 jours, il ne devrait pas
        # être à jour de cotistion
        self.assertEqual(member.is_paid_up(), False)

        # Créé une cotisation actuelle
        membershipfee = MembershipFee(member=member, amount=20,
                                      start_date=datetime.date.today() -
                                      datetime.timedelta(10),
                                      end_date=datetime.date.today() + datetime.timedelta(10))
        membershipfee.save()
        # La cotisation se terminant dans 10 jour, il devrait être à jour
        # de cotisation
        self.assertEqual(member.is_paid_up(), True)


class MemberAdminTests(TestCase):

    def setUp(self):
        #~ Client web
        self.client = Client()
        #~ Créé un superuser
        self.admin_user_password = '1234'
        self.admin_user = Member.objects.create_superuser(
            username='test_admin_user',
            first_name='test',
            last_name='Admin user',
            email='i@mail.com',
            password=self.admin_user_password)
        #~ Connection
        self.assertEqual(self.client.login(
            username=self.admin_user.username, password=self.admin_user_password), True)

    def tearDown(self):
        # Supprime le superuser
        self.admin_user.delete()

    def test_cant_change_username_when_editing(self):
        """
        Vérifie que dans l'admin Django, le champ username n'est pad modifiable
        sur une fiche existante
        """
        #~ Créé un membre
        first_name = 'Gérard'
        last_name = 'Majax'
        username = MemberTestsUtils.get_random_username()
        member = Member(first_name=first_name,
                        last_name=last_name, username=username)
        member.save()

        edit_page = self.client.get('/admin/members/member/%i/' % member.id)
        self.assertNotContains(edit_page,
                               '''<input id="id_username" />''',
                               html=True)

        member.delete()


class MemberTestsUtils(object):

    @staticmethod
    def get_random_username():
        """
        Renvoi une clé aléatoire pour un utilisateur LDAP
        """
        return 'coin_test_' + os.urandom(8).encode('hex')
