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

from __future__ import unicode_literals

from contextlib import contextmanager
import subprocess

from django.conf import settings
from django.db import models
from django.db.models.signals import pre_save, post_save, post_delete
from django.dispatch import receiver

from coin.members.models import Member


class SyncCommandError(SystemError):
    pass

class MaillingListSubscription(models.Model):
    member = models.ForeignKey(Member, verbose_name='membre')
    maillinglist = models.ForeignKey('MaillingList', verbose_name='liste mail')

    class Meta:
        verbose_name = 'abonnement à une liste mail'
        verbose_name_plural = 'abonnements aux listes mail'
        unique_together = ('member', 'maillinglist')

    def __str__(self):
        return str(self.maillinglist)

class MaillingList(models.Model):
    short_name = models.CharField(
        'identifiant technique', max_length=50, unique=True,
        help_text=(
            "c'est l'identifiant qui servira à "
            "communiquer avec le serveur de liste mail "
            "(typiquement, la partie avant le \"@\" dans l'adress )"
        )
    )
    email = models.EmailField("adresse mail d'envoi", unique=True)
    verbose_name = models.CharField(
        'nom complet', max_length=130,
        help_text="Nom affiché dans l'interface membre"
    )
    description = models.TextField()
    subscribers = models.ManyToManyField(
        Member, related_name='subscribed_maillinglists',
        through=MaillingListSubscription,
        verbose_name='abonné·e·s', blank=True)

    class Meta:
        verbose_name = 'liste mail'
        verbose_name_plural = 'listes mail'

    def __unicode__(self):
        return '{} ({})'.format(self.verbose_name, self.email)

    def as_text_listing(self):
        """ One subscriber email per line
        """
        return '\n'.join(
                self.subscribers.values_list('email', flat=True))

    def sync_to_list_server(self, force_clear=False):
        if not settings.MAILLIST_SYNC_COMMAND:
            raise ValueError('You should define MAILLIST_SYNC_COMMAND'
                             ' setting to use maillist module')
        else:
            cmd = settings.MAILLIST_SYNC_COMMAND.format(
                email=self.email,
                short_name=self.short_name,
            )
            p = subprocess.Popen(
                cmd, shell=True,
                stdin=subprocess.PIPE, stderr=subprocess.PIPE)
            if force_clear:
                text_listing = ''
            else:
                text_listing = self.as_text_listing()

            out_stdout, out_stderr = p.communicate(text_listing)
            if p.returncode != 0:
                raise SyncCommandError(
                    "Erreur à l'appel de la commande : \"{}\"".format(
                        out_stderr.decode('utf-8')))


def push_new_subscription(sender, instance, created, raw, *args, **kwargs):
    if raw:
        print("The synchronization of mailling list with Coin was not performed, please launch it by hand in the admin interface.")
    elif not getattr(sender, 'skip_sync', False):
        instance.maillinglist.sync_to_list_server()


def push_remove_subscription(sender, instance, *args, **kwargs):
    if not getattr(sender, 'skip_sync', False):
        instance.maillinglist.sync_to_list_server()


def store_previous_email(sender, instance, *args, **kwargs):
    """Record the email address for post_save handler

    update_an_email_address needs the old email address for comparison, but
    this information is not available at post_save stage.
    """
    member = instance

    # if not, this is a user creation, nothing to do
    if member.pk:
        old_member = Member.objects.get(pk=member.pk)
        member._previous_email = old_member.email


@receiver(post_save, sender=Member)
def update_an_email_address(sender, instance, *args, **kwargs):
    """Check if the member email has changed and sync mail lists if so.

    We do that at post_save stage because we need the new information to be
    recorded in database, otherwise, sync_list_to_server() would use the old
    email.
    """
    member = instance
    old_email = getattr(member, '_previous_email', None)

    if old_email and (old_email != member.email):
        for maillist in member.subscribed_maillinglists.all():
            maillist.sync_to_list_server()
    # Error handling ???
    # try:
    # except SyncCommandError as e:
    #     print("error", e)
    # we cannot send a message because we don't have the request


SIGNALS = [
    (Member, pre_save, store_previous_email),
    (Member, post_save, update_an_email_address),
    (MaillingListSubscription, post_save, push_new_subscription),
    (MaillingListSubscription, post_delete, push_remove_subscription),
]


def connect_signals():
    for sender, signal, receiver in SIGNALS:
        signal.connect(sender=sender, receiver=receiver)


def disconnect_signals():
    for sender, signal, receiver in SIGNALS:
        signal.disconnect(sender=sender, receiver=receiver)


# Do it once
connect_signals()


@contextmanager
def skip_maillist_sync():
    """ Allows to skip temporary signals
    """
    disconnect_signals()
    try:
        yield
    finally:
        connect_signals()