from django.db import models
from django.core.mail import EmailMultiAlternatives
from django.template import Context, Template
from django.contrib.contenttypes.models import ContentType

try:
    # Django>=1.7
    from django.contrib.contenttypes.fields import GenericForeignKey
except ImportError:
    from django.contrib.contenttypes.generic import GenericForeignKey
    
    

from conf import settings
from fields import SeparatedValuesField, validate_template_syntax

class EmailMessageTemplateManager(models.Manager):

    def get_template(self, name, related_object=None):
        """
        If a related object is specified, we'll first try to retrieve a template 
        that matches both the name and the object, and if none exists, we'll try 
        to retrieve a template with the specified name without a related object 
        instead (to support situations where some objects have a specialized 
        template, but, when none exists, we want to fall back to a default). 
        """
        if related_object:
            object_id = related_object.pk
            content_type = ContentType.objects.get_for_model(related_object)
        else:
            object_id = None
            content_type = None
        
        try:
            return self.get(name=name, object_id=object_id,
                            content_type=content_type, enabled=True)
        except EmailMessageTemplate.DoesNotExist:
            if not related_object:
                raise
            return self.get(name=name, object_id=None, content_type=None,
                            enabled=True)


class EmailMessageTemplate(models.Model, EmailMultiAlternatives):
    """
    A template for an email to be sent by the system.  Also a subclass of 
    Django's EmailMessage, so its methods can be used for sending.
    """
    
    CONTENT_TYPE_CHOICES = (('text/plain', 'Text',),
                            ('text/html', 'HTML',),)

    #Fields to identify a template
    name = models.CharField(max_length=50)
    content_type = models.ForeignKey(ContentType, null=True, blank=True, verbose_name="Related Object Type")
    object_id = models.PositiveIntegerField(null=True, blank=True, verbose_name="Related Object ID")
    related_object = GenericForeignKey('content_type', 'object_id')

    #Fields to override from EmailMessage
    type = models.CharField(max_length=20, choices=CONTENT_TYPE_CHOICES, default='text/plain', help_text="Should email content generated by this template be sent as plain text or HTML?")
    subject_template = models.CharField(max_length=2000, validators=[validate_template_syntax])
    body_template_html = models.TextField(validators=[validate_template_syntax], blank=True, verbose_name="HTML body template")
    autogenerate_text = models.BooleanField(default=True, help_text="If checked, this option will cause a plain text version of the HTML email to automatically be created when it is sent.  If you'd like to write the plain text template manually, uncheck this box.")
    body_template = models.TextField(validators=[validate_template_syntax], blank=True)
    sender = models.EmailField(max_length=75, blank=True, default='', verbose_name="'From' Email", help_text="The address this email should appear to be sent 'from'.  If blank, defaults to '{0}'.".format(settings.EMAILMESSAGETEMPLATES_DEFAULT_FROM_EMAIL))
    base_cc = SeparatedValuesField(blank=True, default='', verbose_name="CC", help_text="An optional list of email addresses to be CCed when this template is sent (in addition to any addresses specified when the message is sent)")
    base_bcc = SeparatedValuesField(blank=True, default='', verbose_name="BCC", help_text="An optional list of email addresses to be BCCed when this template is sent (in addition to any addresses specified when the message is sent)")

    #Other information
    description = models.TextField()
    enabled = models.BooleanField(default=True, help_text="When unchecked, this email will not be sent.")
    edited_date = models.DateTimeField(auto_now=True, editable=False, blank=True)
    edited_user = models.TextField(max_length=30, editable=False, blank=True)

    objects = EmailMessageTemplateManager()

    #Ensure compatibility with EmailMessage CC, BCC, and from_email data
    _instance_to = []
    _instance_cc = []
    _instance_bcc = []
    _instance_from = None

    @property
    def to(self):
        """
        The recipient addresses specified on the instance.
        """
        return self._instance_to

    @to.setter
    def to(self, value):
        """
        Set the recipient addresses on the instance.
        """
        self._instance_to = value

    @property
    def cc(self):
        """
        The unique set of CC addresses specified either in the template or on 
        the instance.
        """
        return list(set(self._instance_cc) | set(self.base_cc if self.base_cc is not None else []))

    @cc.setter
    def cc(self, value):
        """
        Add any addresses not in the template's CC list to the instance list.
        """
        self._instance_cc = list(set(value) - set(self.base_cc if self.base_cc is not None else []))

    @property
    def bcc(self):
        """
        The unique set of BCC addresses specified either in the template or on 
        the instance.
        """
        return list(set(self._instance_bcc) | set(self.base_bcc if self.base_bcc is not None else []))

    @bcc.setter
    def bcc(self, value):
        """
        Add any addresses not in the template's BCC list to the instance list.
        """
        self._instance_bcc = list(set(value) - set(self.base_bcc if self.base_bcc is not None else []))

    @property
    def from_email(self):
        """
        Use the specified sender if set, and then fall back to the template 
        sender, and finally the setting value.
        """
        return self._instance_from or self.sender or\
            settings.EMAILMESSAGETEMPLATES_DEFAULT_FROM_EMAIL or\
            settings.DEFAULT_FROM_EMAIL

    @from_email.setter
    def from_email(self, value):
        """
        Set the sender addresses on the instance.
        """
        self._instance_from = value

    def __unicode__(self):
        status = " (Disabled)" if not self.enabled else ""
        if self.related_object:
            return "{0} for {1}{2}".format(self.name, self.related_object, status)
        return self.name + status

    # Preparing and sending messages
    _context = Context({})
    subject_prefix = ""

    @property
    def context(self):
        return self._context

    @context.setter
    def context(self, value):
        if issubclass(type(value), Context):
            self._context = value
        else:
            self._context = Context(value)

    @property
    def subject(self):
        return self.subject_prefix + Template(self.subject_template).render(self.context)

    @subject.setter
    def subject(self, value):
        """
        No-op to prevent EmailMessage from stomping on the template 
        """
        pass

    @property
    def body(self):
        if self.is_html_message() and self.autogenerate_text:
            try:
                import html2text
                return html2text.html2text(self.html_content())
            except ImportError:
                pass
        return Template(self.body_template).render(self.context)

    @body.setter
    def body(self, value):
        """
        No-op to prevent EmailMessage from stomping on the template 
        """
        pass
    
    def html_content(self):
        """
        Render the HTML message content, if any
        """
        if self.is_html_message():
            return Template(self.body_template_html).render(self.context)
        return None
    
    def is_html_message(self):
        return settings.EMAILMESSAGETEMPLATES_ALLOW_HTML_MESSAGES \
            and self.type == 'text/html'
        
                        
    def send(self, fail_silently=False):
        """
        Sends the email message.
        """
        result = None
        send_error = None
        try:
            html_content = self.html_content()
            if html_content is not None:
                self.attach_alternative(html_content, "text/html")
            result = super(EmailMessageTemplate, self).send(fail_silently=False)
        except Exception as e:
            raise
            send_error = e

        #Raise an exception if the user has requested it
        if not fail_silently and send_error:
            raise send_error

        return result
    
    def related_item_display(self):
        return unicode(self.related_object) if self.related_object else 'None'
    related_item_display.short_description = "Related Item"

    def __init__(self,*args,**kwargs):
        """
        Initialize the template, ensuring that the from_email is not set 
        unnecessarily
        """
        super(EmailMessageTemplate, self).__init__(*args,**kwargs)
        #resetting sender to default so EmailMessage won't stomp on it
        self.from_email = None
        

    class Meta:
        ordering = ('name',)
        unique_together = (("name", "content_type", "object_id"),)
        verbose_name = "Email Template"
        app_label = "emailmessagetemplates"
