# -*- coding: utf-8 -*-
import ldapdb.models
import pprint
import os
import base64
import hashlib
import unicodedata
import string
import datetime
from django.db import models
from ldapdb.models.fields import CharField, IntegerField, ListField
from django.db.models.signals import post_save, pre_save, post_delete
from django.dispatch import receiver
from south.modelsinspector import add_ignored_fields
from django.core import exceptions
import logging


class Member(models.Model):

    MEMBER_TYPE_CHOICES = (
        ('personne_physique', 'Personne physique'),
        ('personne_morale', 'Personne morale'),
    )
    MEMBER_STATUS_CHOICES = (
        ('adherent', 'Adhérent'),
        ('non_adherent', 'Non adhérent'),
        ('demande_adhesion', "Demande d'adhésion"),
    )

    status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
                              default='non_adherent')
    type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
                            default='personne_physique')
    first_name = models.CharField(max_length=200, verbose_name=u'Prénom')
    last_name = models.CharField(max_length=200, verbose_name=u'Nom')
    ldap_cn = models.CharField(max_length=200, blank=True,
        help_text='Clé avec le LDAP. Laisser vide pour la générer'
                  'automatiquement')
    organization_name = models.CharField(max_length=200, blank=True,
        verbose_name='Nom de l\'organisme',
        help_text='Pour une personne morale')
    email = models.EmailField(max_length=254, verbose_name=u'Courriel')
    home_phone_number = models.CharField(max_length=25, blank=True,
                                         verbose_name=u'Téléphone fixe')
    mobile_phone_number = models.CharField(max_length=25, blank=True,
                                           verbose_name=u'Téléphone mobile')
    address = models.TextField(verbose_name=u'Adresse')
    postal_code = models.CharField(max_length=15,
                                   verbose_name=u'Code postal')
    city = models.CharField(max_length=200,
                            verbose_name=u'Commune')
    country = models.CharField(max_length=200,
                               default='France',
                               verbose_name=u'Pays')
    entry_date = models.DateField(null=False,
                                  blank=False,
                                  default=datetime.date.today,
                                  verbose_name='Date de première adhésion')
    resign_date = models.DateField(null=True, blank=True,
        verbose_name='Date de départ de l\'association')

    def __unicode__(self):
        name = self.first_name + ' ' + self.last_name
        if (self.organization_name):
            name += ' (%s)' % self.organization_name
        return name

    # Renvoi la date de fin de la dernière cotisation du membre
    def end_date_of_membership(self):
        try:
            return self.membership_fees.order_by('-end_date')[0].end_date
        except:
            return None

    def change_password(self, new_password):
        ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
        ldap_user.password = new_password
        ldap_user.save()


class CryptoKey(models.Model):

    KEY_TYPE_CHOICES = (('RSA', 'RSA'), ('GPG', 'GPG'))

    type = models.CharField(max_length=3, choices=KEY_TYPE_CHOICES)
    key = models.TextField(verbose_name=u'Clé')
    member = models.ForeignKey('Member', verbose_name=u'Membre')

    def __unicode__(self):
        return u'Clé %s de %s' % (self.type, self.member)


class MembershipFee(models.Model):
    member = models.ForeignKey('Member', related_name='membership_fees',
                               verbose_name=u'Membre')
    amount = models.IntegerField(null=False, default='20', help_text='en €',
                                 verbose_name=u'Montant')
    start_date = models.DateField(
        null=False,
        blank=False,
        default=datetime.date.today,
        verbose_name='Date de début de cotisation')
    end_date = models.DateField(
        null=False,
        blank=False,
        default=datetime.date.today() + datetime.timedelta(365),
        verbose_name='Date de fin de cotisation')

    def __unicode__(self):
        return (u'%s - %s - %i€' % (self.member, self.start_date,
                                     self.amount))


class LdapUser(ldapdb.models.Model):
    # TODO: déplacer ligne suivante dans settings.py
    base_dn = "ou=users,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
    object_classes = ['inetOrgPerson', 'organizationalPerson', 'person',
                      'top', 'posixAccount']

    uid = CharField(db_column='uid', unique=True, max_length=255)
    nick_name = CharField(db_column='cn', unique=True, primary_key=True,
                          max_length=255)
    first_name = CharField(db_column='givenName', max_length=255)
    last_name = CharField(db_column='sn', max_length=255)
    display_name = CharField(db_column='displayName', max_length=255,
                             blank=True)
    password = CharField(db_column='userPassword', max_length=255)
    uidNumber = IntegerField(db_column='uidNumber', unique=True)
    gidNumber = IntegerField(db_column='gidNumber', default=2000)
    homeDirectory = CharField(db_column='homeDirectory', max_length=255,
                              default='/tmp')

    def __unicode__(self):
        return self.display_name

    class Meta:
        managed = False  # Indique à South de ne pas gérer le model LdapUser


class LdapGroup(ldapdb.models.Model):
    """
    Class for representing an LDAP group entry.
    """
    # LDAP meta-data
    base_dn = "ou=groups,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
    object_classes = ['posixGroup']

    # posixGroup attributes
    gid = IntegerField(db_column='gidNumber', unique=True)
    name = CharField(db_column='cn', max_length=200, primary_key=True)
    members = ListField(db_column='memberUid')

    def __unicode__(self):
        return self.name

    class Meta:
        managed = False  # Indique à South de ne pas gérer le model LdapGroup

#Indique à South de ne pas gérer les models LdapUser et LdapGroup
add_ignored_fields(["^ldapdb\.models\.fields"])


@receiver(pre_save, sender=LdapUser)
def change_password(sender, instance, **kwargs):
    """
    Lors de la sauvegarde d'un utilisateur Ldap, cette fonction est exécutée
    avant la sauvegare pour chiffrer le mot de passe s'il est définit
    et s'il n'est pas déjà chiffré
    """
    # Si le mot de passe est définit et n'est pas déjà chiffré,
    # alors ça le chiffre
    if instance.password and not instance.password.startswith('{SSHA}'):
        salt = os.urandom(8).encode('hex')
        digest = hashlib.sha1(instance.password + salt).digest()
        instance.password = '{SSHA}' + base64.b64encode(digest + salt)


@receiver(pre_save, sender=LdapUser)
def define_display_name(sender, instance, **kwargs):
    """
    Lors de la sauvegarde d'un utilisateur Ldap, le champ display_name est la
    concaténation de first_name et last_name
    """
    if not instance.display_name:
        instance.display_name = '%s %s' % (instance.first_name,
                                           instance.last_name)


@receiver(pre_save, sender=Member)
def define_ldap_cn(sender, instance, **kwargs):
    """
    Lors de la sauvegarde d'un membre. Si le champ ldap_cn n'est pas définit,
    le calcul automatiquement en fonction du nom et du prénom
    """
    if not instance.ldap_cn:
        # Première lettre de chaque partie du prénom
        first_name_letters = ''.join(
            [c[0] for c in instance.first_name.split('-')]
        )
        # Concaténer avec nom de famille
        ldap_cn = ('%s%s' % (first_name_letters, instance.last_name))
        # Remplacer ou enlever les caractères non ascii
        ldap_cn = unicodedata.normalize('NFD', ldap_cn)\
            .encode('ascii', 'ignore')
        # Enlever ponctuation et espace
        ldap_cn = ldap_cn.translate(None, string.punctuation + ' ')
        # En minuscule
        ldap_cn = ldap_cn.lower()

        instance.ldap_cn = ldap_cn


@receiver(post_save, sender=Member)
def sync_ldap(sender, instance, created, **kwargs):
    """
    Update LDAP data when a member is saved
    """
    if not created:
        ldap_user = LdapUser.objects.get(pk=instance.ldap_cn)

    if created:
        max_uidNumber = LdapUser.objects.order_by('-uidNumber')[0].uidNumber

        ldap_user = LdapUser()
        ldap_user.pk = instance.ldap_cn
        ldap_user.uid = instance.ldap_cn
        ldap_user.nick_name = instance.ldap_cn
        ldap_user.uidNumber = max_uidNumber + 1

    ldap_user.last_name = instance.last_name
    ldap_user.first_name = instance.first_name
    ldap_user.save()

    if created:
        ldap_group = LdapGroup.objects.get(pk='coin')
        ldap_group.members.append(ldap_user.pk)
        ldap_group.save()


@receiver(post_delete, sender=Member)
def remove_ldap_user_from_coin_group_when_deleting_member(sender,
                                                                 instance,
                                                                 **kwargs):
    """
    Lorsqu'un membre est supprimé du SI, son utilisateur LDAP correspondant est
    sorti du groupe "coin"
    """
    ldap_group = LdapGroup.objects.get(pk='coin')
    if instance.ldap_cn in ldap_group.members:
        ldap_group.members.remove(instance.ldap_cn)
        ldap_group.save()

#==============================================================================
# @receiver(pre_save, sender = LdapUser)
# def ssha_password(sender, **kwargs):
#     if not kwargs['instance'].password.startswith('{SSHA}'):
#         salt = os.urandom(8).encode('hex')
#         kwargs['instance'].password = '{SSHA}' + base64.b64encode(
    #         hashlib.sha1(obj.password + salt).digest() + salt)
#==============================================================================
