# -*- coding: utf-8 -*-

"""
Request and document
"""

import os.path
import datetime
import decimal
import logging
import re

from django.db import models
from django.utils.translation import ugettext_lazy as _, string_concat
from django.core import validators
from django.template import Template, Context
from django.core.mail import EmailMessage
from django.utils.timezone import now
from django.core.urlresolvers import reverse
from django.conf import settings

from ... import utils
from .. import app_settings
from .. import fields

import profile
from user import User # we use a proxy user model to get ldap attribute retrieval
import workflow

TELEPHONE_VALIDATOR = validators.RegexValidator(regex=r'[0-9. ]*',
        message=_(u'Les numéros de téléphone ne peuvent contenir ' 
                  u'que des chiffres, des points ou des espaces'))

logger = logging.getLogger(__name__)

class DocumentUsage(models.Model):
    class Meta:
        app_label = 'base'
        verbose_name = _(u"Usage d'un document")
        verbose_name_plural = _(u"Usages d'un document")
        ordering = ('order', 'name')

    def __unicode__(self):
        return self.name

    name = models.CharField(max_length=64, verbose_name=_(u'Usage'))
    description = models.TextField(verbose_name=_(u'Description'), blank=True)
    url = models.URLField(max_length=200, verbose_name=_(u'URL'), blank=True)
    comments = models.TextField(verbose_name=_(u'Remarques et commentaires'), blank=True)
    order = models.IntegerField(verbose_name=_(u'Ordre'), default=0)
    no_diffusion = models.BooleanField(verbose_name=_(u'Ne pas diffuser'),
            help_text=_(u'Si coché, pour les documents ayant cet usage, la diffusion ne sera pas proposée.'),
            default=False, blank=True)

class DocumentLicence(models.Model):

    class Meta:
        app_label = 'base'
        verbose_name = _(u"Diffusion d'un document")
        verbose_name_plural = _(u"Diffusions d'un document")
        ordering = ('order', 'name',)

    def __unicode__(self):
        return self.name

    name = models.CharField(max_length=64, verbose_name=_(u'Droit de diffusion'))
    description = models.TextField(verbose_name=_(u'Description'), blank=True)
    url = models.URLField(max_length=200, verbose_name=_(u'URL'), blank=True)
    comments = models.TextField(verbose_name=_(u'Remarques et commentaires'), blank=True)
    order = models.IntegerField(verbose_name=_(u'Ordre'), default=0)
    default = models.BooleanField(verbose_name=_(u'Valeur par défaut'),
            blank=True, default=False)
    # Si le document contient des extraits copyrightés, on ne peut pas diffuser le document
    only_free_documents = models.BooleanField(verbose_name=_(u'Réservé aux'
        u' documents libres de droits'), blank=True, default=False)

    # pour ajouter un nouveau type de diffusion, l'ajouter ici
    DIFFUSION_TAGS = [
            ('oai-pmh', _(u'Pousser le document dans le flux OAI-PMH')),
            ('public-url', _(u'Le document possède une URL publique')),
    ]

    @classmethod
    def add_diffusion_tag(cls, tag, description):
        for t, d in cls.DIFFUSION_TAGS:
            if t == tag:
                return
        cls.DIFFUSION_TAGS.append((tag, description))

    diffusion_tags = fields.MultiSelectField(
            verbose_name=_(u"Tags de diffusion"),
            help_text=_(u"Ce sont les options diverses que l'ont peut "
                u"associer à ce type de diffusion"),
            blank=True,
            choices=DIFFUSION_TAGS)


class DeliveryPlace(models.Model):
    class Meta:
        app_label = 'base'
        verbose_name = _(u"Lieu de livraison")
        verbose_name_plural = _(u"Lieux de livraison")
        ordering = ('order', 'name',)

    def __unicode__(self):
        return self.name

    order = models.IntegerField(verbose_name=_(u'Ordre'), default=0)
    name = models.CharField(max_length=64, verbose_name=_(u'Lieu de livraison'))
    description = models.TextField(verbose_name=_(u'Description'), blank=True)
    url = models.URLField(max_length=200, verbose_name=_(u'URL'), blank=True)
    comments = models.TextField(verbose_name=_(u'Remarques et commentaires'), blank=True)

def get_default_status():
    '''Retourne le seul et unique status par défaut, sinon une erreur.'''
    return workflow.Status.objects.get(default=True)

def minimum_delivery_timedelta():
    return datetime.timedelta(days=app_settings.MINIMUM_DELIVERY_DELAY)

class RequestQueryset(utils.QuerySet):
    def total_cost(self):
        zero = decimal.Decimal(0)
        # special case for Postgres so that it is fast in production
        if utils.qs_use_postgres(self):
            cost_function1 = '({1}.ppp * {0}.nb_pages + {1}.ppd)*{0}.copies'.format(
                    Request._meta.db_table, profile.Profile._meta.db_table)
            cost_function2 = '''SELECT SUM(({1}.ppp * {0}.nb_pages + {1}.ppd)*{0}.copies) FROM {1}, {2}
                WHERE {1}.id = {2}.profileoptionchoice_id AND {2}.request_id = {0}.id'''.format(
                    Request._meta.db_table, profile.ProfileOptionChoice._meta.db_table,
                    Request._meta.get_field('choices').rel.through._meta.db_table)

            qs1 = self \
                    .prefetch_related(None) \
                    .select_related(depth=0) \
                    .extra(select={
                        'base_profile_estimated_cost' : cost_function1,
                        'choices_estimated_cost': cost_function2
                    })
            qs1.query.join((Request._meta.db_table, profile.Profile._meta.db_table,
                Request._meta.get_field('base_profile').column,
                profile.Profile._meta.get_field('id').column))
            return sum([r.cost or ((r.base_profile_estimated_cost or zero)+(r.choices_estimated_cost or zero)) for r in qs1])
        else:
            # generic implementation working everywhere
            qs = self.select_related(depth=0) \
                    .select_related('base_profile') \
                    .prefetch_related(None) \
                    .prefetch_related('choices') \
                    .only('nb_pages', 'copies', 'base_profile__ppp',
                            'base_profile__ppd', 'choices__ppp', 'choices__ppd')
            total_cost = zero
            for r in [ r for r in qs if r.base_profile and (r.get_copies() > 0) ]:
                ppp = r.base_profile.ppp
                ppd = r.base_profile.ppd
                for c in r.choices.all():
                    ppp += c.ppp
                    ppd += c.ppd
                total_cost += ((r.nb_pages * ppp) + ppd) * r.get_copies()
            return total_cost

class Counter(models.Model):
    month = models.DateField(unique=True)
    counter = models.IntegerField(default=0)

    class Meta:
        app_label = 'base'

class Request(models.Model):
    COPYRIGHT_CHOICES = (
        (False, _(
            u"Le document ne contient pas d'extrait d'oeuvre soumise"
            u" au droit d'auteur")),
        (True, _(
            u"Le document contient un ou des extrait(s) d'oeuvres(s)"
            u"soumise(s) au droit d'auteur")),
        )

    NUMBER_RE = re.compile(r'(\d\d\d\d)-(\d+)-(\d+)')
    FINANCIAL_CODE_RE = re.compile(r'\d{6,}')

    class Meta:
        app_label = 'base'
        verbose_name = _(u'Demande de reprographie');
        verbose_name_plural = _(u'Demandes de reprographie')
        ordering = ('-creation_date','-month_order')

    objects = RequestQueryset.as_manager()

    user = models.ForeignKey(User, verbose_name=_(u'Demandeur'))
    month_order = models.IntegerField(verbose_name=_(u'Numéro'), default=-1,
            unique_for_month='creation_date')
    name = models.TextField(verbose_name=_(u'Titre'), blank=True)
    uploadfile = models.FileField(upload_to='upload', verbose_name=_(u'Fichier'), blank=True, max_length=512)
    nb_pages = models.PositiveIntegerField(verbose_name=_(u'Nombre de pages'), blank=True, null=True, default=0,
            help_text=_(u"""Une information exacte sur le nombre de pages et le titre facilitera la tâche du Service de Reprographie."""))
    usage = models.ForeignKey(DocumentUsage, verbose_name=_(u'Usage du document'), blank=True, null=True, on_delete=models.PROTECT,
        help_text=_(u"""L’usage de votre document sera utile si vous acceptez de l’envoyer à la Bibliothèque (Etape 6)."""))
    licence = models.ForeignKey(DocumentLicence, verbose_name=_(u'Droits de diffusion'), blank=True, null=True, on_delete=models.PROTECT)
    copyright = models.NullBooleanField(verbose_name=_(u"Déclaration de présence d'extraits protégés"), choices=COPYRIGHT_CHOICES, blank=True, null=True)
    comments = models.TextField(verbose_name=_(u'Remarques et commentaires'), blank=True)
    sponsor = models.CharField(max_length=128, verbose_name=_(u'Commanditaire'), help_text=_(u"Complétez cette information si vous faites cette demande pour un tiers."), blank=True)
    entity = models.ForeignKey('Entity', verbose_name=_(u'Destinataires de ce document'), on_delete=models.PROTECT, blank=True, null=True,
            help_text=_(u"""Renseigner le diplôme ou l'UE concernée permettra un meilleur suivi de votre document."""))
    copies = models.PositiveIntegerField(verbose_name=_(u'Nombre d\'exemplaires'), blank=True, null=True, default=1, validators=[validators.MinValueValidator(1)])
    status = models.ForeignKey(workflow.Status, verbose_name=_(u'Status'), on_delete=models.PROTECT)
    base_profile = models.ForeignKey('Profile',
            verbose_name=_(u'Profil de reprographie'),
            help_text = _(u"Choissez le profil de base, puis complétez si "
                u"besoin vos choix dans les options ci-dessous."),
            blank=True, null=True, on_delete=models.PROTECT,
            limit_choices_to={'visible': True})
    choices = models.ManyToManyField('ProfileOptionChoice', verbose_name=_(u'Tous les choix de reprographie'), blank=True)
    details = models.TextField(verbose_name=_(u'Vos observations ou commentaires'), blank=True)
    delivery_date = models.DateField(verbose_name=_(u'Date de livraison souhaitée'),
            blank=True, null=True,
            help_text=string_concat(_(u'Délai minimum à respecter '), app_settings.MINIMUM_DELIVERY_DELAY, _(' jours.')),
            default=lambda:utils.get_next_workable_day(app_settings.MINIMUM_DELIVERY_DELAY))
    delivery_place = models.ForeignKey(DeliveryPlace, verbose_name=_(u'Lieu de livraison'), blank=True, null=True, on_delete=models.PROTECT)
    comments = models.TextField(verbose_name=_(u'Remarques et commentaires'), blank=True)
    creation_date = models.DateTimeField(verbose_name=_(u'Date de création'), default=now, editable=False, db_index=True)
    modification_date = models.DateTimeField(verbose_name=_(u'Date de modification'), editable=False, db_index=True)
    contact_email = models.EmailField(verbose_name=_(u'Email'), blank=True)
    contact_telephone1 = models.CharField(max_length=32,
        verbose_name=_(u'Téléphone'),
        validators=[TELEPHONE_VALIDATOR],
        blank=True)
    contact_telephone2 = models.CharField(max_length=32,
        verbose_name=_(u'Téléphone (bis)'),
        validators=[TELEPHONE_VALIDATOR],
        blank=True)
    contact_bureau = models.CharField(max_length=64,
            verbose_name=_('Bureau'),
            blank=True)
    financial_code = models.CharField(max_length=64,
            verbose_name=_(u'Code d\'imputation'), blank=True, db_index=True)
    financial_comment = models.TextField(
            verbose_name=_(u"Commentaire pour la facturation"), blank=True)
    cost = models.DecimalField(verbose_name=_(u'Coût'), blank=True, null=True,
            max_digits=10, decimal_places=5)
    is_from_remote_request = models.BooleanField(blank=True,
            verbose_name=_(u'Demande initiée via un Web-Service'),
            default=False)

    def copyright_status(self):
        if self.copyright is None:
            return _(u'Inconnu')
        else:
            return dict(self.COPYRIGHT_CHOICES).get(self.copyright)

    def request_number(self):
        s = unicode(self.creation_date.strftime('%Y-%m-'))
        s += unicode(self.month_order)
        return s.replace(u'-', u'\u2011')

    def request_number_csv(self):
        s = unicode(self.creation_date.strftime('%Y-%m-'))
        s += unicode(self.month_order)
        return s

    def get_copies(self):
        return self.copies or 0

    def get_nb_pages(self):
        return self.nb_pages or 0

    def total_pages(self):
        return self.get_copies() * self.get_nb_pages()

    def sponsor_display(self):
        fn = self.user.get_full_name()
        if not fn:
            fn = self.user.username
        if self.sponsor.strip() and self.sponsor != fn:
            return '%s (%s)' % (fn, self.sponsor.strip())
        return fn

    def sponsor_username(self):
        '''Try to extract a LDAP username from the sponsor field'''
        m = re.search('\((.*)\)', self.sponsor)
        if m:
            return m.group(1)
        return ''

    def estimated_cost(self):
        total = decimal.Decimal(0)
        if self.base_profile:
            total += ((self.base_profile.ppp * self.get_nb_pages()) + self.base_profile.ppd) * self.get_copies()
        for choice in self.choices.all():
            total += ((choice.ppp * self.get_nb_pages()) + choice.ppd) * self.get_copies()
        return total

    def missing(self):
         null = [None, '', 0]
         required = ['name', 'nb_pages', 'usage',
                 'licence','entity',  'copies', 'base_profile',
                 'delivery_place',  'delivery_date']
         for field in required:
             v = getattr(self, field, None)
             for n in null:
                 if v is n:
                     yield field
         if not self.no_diffusion() and self.copyright is None:
             yield 'copyright'

    def validate(self):
        return not bool(list(self.missing()))

    def save(self, *args, **kwargs):
        self.modification_date = now()
        if self.month_order == -1:
            date = self.creation_date.date()
            date = date.replace(day=1)
            counter, created = Counter.objects.get_or_create(month=date)
            counter = Counter.objects.select_for_update().get(month=date)
            counter.counter += 1
            counter.save()
            self.month_order = counter.counter
        super(Request, self).save(*args, **kwargs)

    def act(self, who, workflow, description=None):
        from .. import rbac
        history = self.history_set.all()
        ctx = {
                'request': self,
                'actor': who,
                'comment': description,
                'history': history,
                'reversed_history': reversed(history),
                'request_url': settings.SITE_URL + self.get_absolute_url(),
              }
        ctx = Context(ctx)
        logger.info('action %s par %s avec le commentaire %s sur la demande %s',
                unicode(workflow).encode('utf-8'),
                unicode(who).encode('utf-8'),
                unicode(description).encode('utf-8'),
                unicode(self.request_number()).encode('utf-8'))
        for mail_notification in workflow.action.mailnotification_set.all():
            emails = []
            if mail_notification.to == 'requestor':
                if self.user.email:
                    emails = [ self.user.email ]
            elif mail_notification.to == 'last_actor':
                for history in reversed(history):
                    if history.old_status != history.new_status and history.user.email:
                        emails = [ history.user.email ]
                        break
            elif mail_notification.to == 'all_actors':
                emails = list(set([h.user.email for h in history]) - set([who.email]))
            elif mail_notification.to == 'next_actors':
                emails = set(u.email for u in rbac.get_actors(workflow.destination, self.entity))
            else:
                raise NotImplementedError
            # remove empty strings
            emails = filter(None, emails)
            if not emails:
                continue
            prefix = '{% load url from future %}'
            subject = Template(prefix+mail_notification.subject_template).render(ctx)
            body = Template(prefix+mail_notification.body_template).render(ctx)
            logger.info("envoi d'un mail à %s",
                    (', '.join(emails)).encode('utf-8'))
            email = EmailMessage(subject=subject, body=body, to=emails)
            try:
                email.send()
            except:
                logger.exception("l'envoi du mail a echoue")

        History.objects.create(request=self,
                user=who,
                old_status=self.status,
                new_status=workflow.destination,
                action=workflow.action,
                description=description or '')

        self.status = workflow.destination
        self.save()

    def file_name(self):
        if self.uploadfile:
            return os.path.basename(self.uploadfile.name)
        return ''

    def sponsor_name(self):
        sponsor_name = re.match(r'[^(]*', self.sponsor).group(0).strip()
        if not sponsor_name:
            sponsor_name = self.user.display_name()
        return sponsor_name

    def is_paper(self):
        return not bool(self.uploadfile)

    def no_diffusion(self):
        return self.is_from_remote_request or \
            (self.usage and self.usage.no_diffusion) \
            or self.is_paper()

    def clean(self):
        if self.no_diffusion():
            self.licence = DocumentLicence(id=1)

    def __unicode__(self):
        return '<Request id:%s>' % (self.pk,)

    def get_absolute_url(self):
        return reverse('request_detail', kwargs={'pk': self.pk})

class HistoryManager(models.Manager):
    def start(self):
        try:
            return self.filter(new_status__start=True).latest()
        except History.DoesNotExist:
            return None

    def start_date(self):
        start = self.start()
        if start:
            return start.date
        else:
            return now()

    def end(self):
        end = None
        for history in self.select_related('new_status').reverse():
            if history.new_status.end:
                end = history
            elif end is not None:
                return end
            else:
                return None

    def end_date(self):
        end = self.end()
        if end is None:
            return None
        else:
            return end.date

    def last_show_in_details(self):
        for h in self.all().reverse():
            if h.action.special_type == 'show_in_details':
                return h
        return None

history_manager = HistoryManager()
history_manager.use_for_related_fields = True

class History(models.Model):
    objects = history_manager

    class Meta:
        app_label = 'base'
        verbose_name = _(u"Historique")
        verbose_name_plural = _(u"Historiques des demandes")
        ordering = ('date',)
        get_latest_by = 'date'

    def __unicode__(self):
        return self.date.strftime('%Y-%m-%d %H:%M:%S ') + unicode(self.action)

    date = models.DateTimeField(auto_now=True, db_index=True)
    request = models.ForeignKey(Request, verbose_name=_(u'Demande'))
    user = models.ForeignKey(User, verbose_name=_(u'Utilisateur'), blank=True, null=True, on_delete=models.SET_NULL)
    action = models.ForeignKey(workflow.Action, verbose_name=_('Action'), on_delete=models.PROTECT)
    old_status = models.ForeignKey(workflow.Status,
            verbose_name=_(u"État de la demande avant l'action"), blank=True,
            related_name='+', null=True, on_delete=models.PROTECT)
    new_status = models.ForeignKey(workflow.Status,
            verbose_name=_(u"État de la demande après l'action"),
            related_name='+', on_delete=models.PROTECT)
    description = models.TextField(verbose_name=_(u"Détails éventuels sur l'action"), blank=True)
