import datetime

from django.dispatch import receiver
from django.contrib.gis.db import models
from django.db.models.signals import post_save
from django.contrib.sites.models import Site
from django.template.loader import select_template
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.text import slugify
from django.urls import reverse
from django.core.mail import send_mail
from django.conf import settings
from django.utils.crypto import get_random_string
from django.core.mail import EmailMultiAlternatives

from django.template import Context, Template

from bs4 import BeautifulSoup

from taggit.managers import TaggableManager

from utils.util import render_images, parse_video, markdown_to_html
from taxonomy.models import TaggedItems
from media.models import LuxImage, LuxImageSize
from books.models import Book
from posts.models import Post 


# Possible actions that user can perform
ACTIONS = ('subscribe', 'unsubscribe', 'update')


def markdown_to_emailhtml(base_html):
    soup = BeautifulSoup(base_html, "lxml")
    for p in soup.find_all('p'):
        p.attrs['style']="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:normal;margin-bottom:1.4em;font-size:17px;line-height:1.5;hyphens:auto;color:#222222;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;"
    for i in soup.find_all('img'):
        i.attrs['width']="720"
        i.attrs['style']="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;display:inline;margin-bottom:0;width:100% !important;max-width:100% !important;height:auto !important;max-height:auto !important;"
    for h in soup.find_all('hr'):
        h.attrs['style']="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:20%;margin-top:40px;margin-bottom:40px;margin-right:0px;margin-left:0px;border-width:0;border-top-width:1px;border-top-style:solid;border-top-color:#ddd;"
    return str(soup)[12:-14]


def make_activation_code():
    """ Generate a unique activation code. """

    # Use Django's crypto get_random_string() instead of rolling our own.
    return get_random_string(length=40)


class Newsletter(models.Model):
    """ A model for Newletters. Might I one day have two? I might. """
    title = models.CharField(max_length=250)
    slug = models.SlugField(db_index=True, unique=True)
    intro = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("lttr:detail", kwargs={"slug": self.slug})

    def subscribe_url(self):
        return reverse('lttr:newsletter_subscribe_request', kwargs={'newsletter_slug': self.slug})

    def unsubscribe_url(self):
        return reverse('newsletter_unsubscribe_request', kwargs={'newsletter_slug': self.slug})

    def update_url(self):
        return reverse('newsletter_update_request', kwargs={'newsletter_slug': self.slug})

    def archive_url(self):
        return reverse('newsletter_archive', kwargs={'newsletter_slug': self.slug})

    def get_subscriptions(self):
        return Subscriber.objects.filter(newsletter=self, subscribed=True)

    def get_template_plain(self):
        return 'lttr/emails/%s_plain_text_email.txt' % self.slug
    
    def get_template_html(self):
        return 'lttr/emails/%s_html_email.html' % self.slug

    def get_templates(self, action):
        """
        Return a subject, text, HTML tuple with e-mail templates for
        a particular action. Returns a tuple with subject, text and e-mail
        template.
        """

        assert action in ACTIONS + ('message', ), 'Unknown action: %s' % action

        # Common substitutions for filenames
        tpl_subst = {
            'action': action,
            'newsletter': self.slug
        }

        # Common root path for all the templates
        tpl_root = 'lttr/message/'

        subject_template = select_template([
            tpl_root + '%(newsletter)s/%(action)s_subject.txt' % tpl_subst,
            tpl_root + '%(action)s_subject.txt' % tpl_subst,
        ])

        text_template = select_template([
            tpl_root + '%(newsletter)s/%(action)s.txt' % tpl_subst,
            tpl_root + '%(action)s.txt' % tpl_subst,
        ])

        html_template = select_template([
            tpl_root + '%(newsletter)s/%(action)s.html' % tpl_subst,
            tpl_root + '%(action)s.html' % tpl_subst,
        ])

        return (subject_template, text_template, html_template)

    def get_sender(self):
        return 'Scott Gilbertson <sng@luxagraf.net>'

    @classmethod
    def get_default(cls):
        try:
            return cls.objects.all()[0].pk
        except IndexError:
            return None


class OldNewsletterMailing(models.Model):
    """ A model for Newletter Mailings, the things actually sent out """
    newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE)
    title = models.CharField(max_length=250)
    subtitle = models.CharField(max_length=250, null=True, blank=True)
    slug = models.SlugField(unique_for_date='pub_date', blank=True)
    body_html = models.TextField(blank=True)
    body_email_html = models.TextField(blank=True)
    body_markdown = models.TextField()
    pub_date = models.DateTimeField()
    featured_image = models.ForeignKey(LuxImage, on_delete=models.CASCADE, null=True, blank=True)
    tags = TaggableManager(through=TaggedItems, blank=True, help_text='Topics Covered')
    PUB_STATUS = (
        (0, 'Not Published'),
        (1, 'Published'),
    )
    status = models.IntegerField(choices=PUB_STATUS, default=0)
    issue = models.PositiveIntegerField()
    date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False)
    books = models.ManyToManyField(Book, related_name="mailing_books", blank=True)

    class Meta:
        ordering = ('-title', '-date_created')

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("lttr:detail", kwargs={"slug": self.newsletter.slug, "issue": self.get_issue_str(), "mailing":self.slug})

    def get_issue_str(self):
        issue = self.issue
        if self.issue < 100:
            issue = "0%s" % self.issue
            if self.issue < 10:
                issue = "00%s" % self.issue
        return issue

    def email_encode(self):
        return self.body_markdown
    
    @property
    def get_previous_published(self):
        return self.get_previous_by_pub_date(status__exact=1,newsletter=self.newsletter)

    @property
    def get_previous_admin_url(self):
        n = self.get_previous_by_pub_date()
        return reverse('admin:%s_%s_change' %(self._meta.app_label,  self._meta.model_name),  args=[n.id] )

    @property
    def get_next_published(self):
        return self.get_next_by_pub_date(status__exact=1, newsletter=self.newsletter)

    @property
    def get_next_admin_url(self):
        model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
        try:
            return reverse('admin:%s_%s_change' %(self._meta.app_label,  self._meta.model_name),  args=[self.get_next_by_pub_date().pk] )
        except model.DoesNotExist:
            return ''

    def save(self, *args, **kwargs):
        created = self.pk is None
        if not created:
            md = render_images(self.body_markdown)
            self.body_html = markdown_to_html(md)
            self.body_email_html = markdown_to_emailhtml(self.body_html)
            self.date_created = timezone.now()
        if created and not self.featured_image:
            self.featured_image = LuxImage.objects.latest()
        old = type(self).objects.get(pk=self.pk) if self.pk else None
        if old and old.featured_image != self.featured_image:  # Field has changed
            s = LuxImageSize.objects.get(name="navigation_thumb")
            ss = LuxImageSize.objects.get(name="picwide-sm")
            self.featured_image.sizes.add(s)
            self.featured_image.sizes.add(ss)
            self.featured_image.save()
        super(NewsletterMailing, self).save()


class NewsletterMailing(models.Model):
    """ A model for Newletter Mailings, the things actually sent out """
    newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.SET_NULL)
    title = models.CharField(max_length=250, blank=True)
    subtitle = models.CharField(max_length=250, null=True, blank=True)
    body_html = models.TextField(blank=True)
    body_email_html = models.TextField(blank=True)
    body_markdown = models.TextField(blank=True)
    pub_date = models.DateTimeField(blank=True)
    featured_image = models.ForeignKey(LuxImage, on_delete=models.CASCADE, null=True, blank=True)
    issue = models.PositiveIntegerField(blank=True)

    class Meta:
        ordering = ('-title', '-pub_date')

    def __str__(self):
        return self.title

    def email_encode(self):
        return self.body_markdown
    
    def get_issue_str(self):
        issue = self.issue
        if self.issue < 100:
            issue = "0%s" % self.issue
            if self.issue < 10:
                issue = "00%s" % self.issue
        return issue
    
    def save(self, *args, **kwargs):
        created = self.pk is None
        if created and self.post:
            self.title = self.post.title
            self.subtitle = self.post.subtitle
            self.body_markdown = self.post.body_markdown
            self.pub_date = self.post.pub_date
            self.featured_image = self.post.featured_image
            self.issue = self.post.issue
        if not created:
            md = render_images(self.body_markdown)
            self.body_html = markdown_to_html(md)
            self.body_email_html = markdown_to_emailhtml(self.body_html)
            self.date_created = timezone.now()
        if created and not self.featured_image:
            self.featured_image = LuxImage.objects.latest()
        super(NewsletterMailing, self).save()


class Subscriber(models.Model):
    """ A model for Newletter Subscriber """
    email_field = models.EmailField(db_column='email', db_index=True, blank=True, null=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
    date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False)
    date_updated = models.DateTimeField(blank=True, auto_now=True, editable=False)
    newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE)
    activation_code = models.CharField(max_length=40, default=make_activation_code)
    subscribed = models.BooleanField(default=False, db_index=True)
    subscribe_date = models.DateTimeField(null=True, blank=True)
    unsubscribed = models.BooleanField(default=False, db_index=True)
    unsubscribe_date = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        if self.user:
            return self.user.username
        return self.email_field

    def get_name(self):
        if self.user:
            return self.user.get_full_name()

    def get_email(self):
        if self.user:
            return self.user.email
        return self.email_field

    def set_email(self, email):
        if not self.user:
            self.email_field = email
    email = property(get_email, set_email)

    def update(self, action):
        """
        Update subscription according to requested action:
        subscribe/unsubscribe/update/, then save the changes.
        """

        assert action in ('subscribe', 'update', 'unsubscribe')

        # If a new subscription or update, make sure it is subscribed
        # Else, unsubscribe
        if action == 'subscribe' or action == 'update':
            self.subscribed = True
        else:
            self.unsubscribed = True

        # This triggers the subscribe() and/or unsubscribe() methods, taking
        # care of stuff like maintaining the (un)subscribe date.
        self.save()

    def _subscribe(self):
        """
        Internal helper method for managing subscription state
        during subscription.
        """

        self.subscribe_date = datetime.datetime.now()
        self.subscribed = True
        self.unsubscribed = False

    def _unsubscribe(self):
        """
        Internal helper method for managing subscription state
        during unsubscription.
        """
        self.subscribed = False
        self.unsubscribed = True
        self.unsubscribe_date = datetime.datetime.now()

    def save(self, *args, **kwargs):
        """
        Perform some basic validation and state maintenance of Subscription.
        TODO: Move this code to a more suitable place (i.e. `clean()`) and
        cleanup the code. Refer to comment below and
        https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.clean
        """

        # This is a lame way to find out if we have changed but using Django
        # API internals is bad practice. This is necessary to discriminate
        # from a state where we have never been subscribed but is mostly for
        # backward compatibility. It might be very useful to make this just
        # one attribute 'subscribe' later. In this case unsubscribed can be
        # replaced by a method property.

        if self.pk:
            assert(Subscriber.objects.filter(pk=self.pk).count() == 1)

            subscription = Subscriber.objects.get(pk=self.pk)
            old_subscribed = subscription.subscribed
            old_unsubscribed = subscription.unsubscribed

            # If we are subscribed now and we used not to be so, subscribe.
            # If we user to be unsubscribed but are not so anymore, subscribe.
            if ((self.subscribed and not old_subscribed) or
               (old_unsubscribed and not self.unsubscribed)):
                self._subscribe()

                assert not self.unsubscribed
                assert self.subscribed

            # If we are unsubcribed now and we used not to be so, unsubscribe.
            # If we used to be subscribed but are not subscribed anymore,
            # unsubscribe.
            elif ((self.unsubscribed and not old_unsubscribed) or
                  (old_subscribed and not self.subscribed)):
                self._unsubscribe()

                assert not self.subscribed
                assert self.unsubscribed
        else:
            if self.subscribed:
                self._subscribe()
            elif self.unsubscribed:
                self._unsubscribe()

        super(Subscriber, self).save(*args, **kwargs)

    def get_recipient(self):
        return get_address(self.name, self.email)

    def send_activation_email(self, action):
        assert action in ACTIONS, 'Unknown action: %s' % action

        (subject_template, text_template, html_template) = \
            self.newsletter.get_templates(action)

        variable_dict = {
            'subscription': self,
            'site': Site.objects.get_current(),
            'newsletter': self.newsletter,
            'date': self.subscribe_date,
            'STATIC_URL': settings.STATIC_URL,
            'MEDIA_URL': settings.MEDIA_URL
        }

        subject = subject_template.render(variable_dict).strip()
        text = text_template.render(variable_dict)

        message = EmailMultiAlternatives(
            subject, text,
            from_email=self.newsletter.get_sender(),
            to=[self.email]
        )

        if html_template:
            message.attach_alternative(
                html_template.render(variable_dict), "text/html"
            )

        message.send()

    def subscribe_activate_url(self):
        return reverse('lttr:newsletter_activate', kwargs={
            'slug': self.newsletter.slug,
            'activation_code': self.activation_code
        })

    def unsubscribe_activate_url(self):
        return reverse('lttr:newsletter_unsubscribe', kwargs={
            'slug': self.newsletter.slug,
            'activation_code': self.activation_code
        })

    def update_activate_url(self):
        return reverse('lttr:newsletter_update_activate', kwargs={
            'slug': self.newsletter.slug,
            'action': 'update',
            'activation_code': self.activation_code
        })


def get_address(name, email):
    if name:
        return u'%s <%s>' % (name, email)
    else:
        return u'%s' % email


class StatusType(models.IntegerChoices):
    INIT = 0, ('Initialized')
    SENT = 1, ('Sent')
    ERROR = 2, ('Error')


class MailingStatus(models.Model):
    newsletter_mailing = models.ForeignKey(NewsletterMailing, on_delete=models.CASCADE, verbose_name=_('newsletter'))
    subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, verbose_name=_('subscriber'))
    status = models.IntegerField(choices=StatusType.choices, null=True)
    creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)

    def newsletter(self):
        return self.newsletter_mailing.newsletter

    def __str__(self):
        return '%s : %s : %s' % (self.newsletter_mailing,
                                 self.subscriber,
                                 self.get_status_display())

    class Meta:
        ordering = ('-creation_date',)
        verbose_name = _('subscriber mailing status')
        verbose_name_plural = _('subscriber mailing statuses')


'''
from lttr.mailer import SendShit
mailing = NewsletterMailing.objects.get(pk=1)
newsletter = Newsletter.objects.get(pk=3)
n = SendShit(newsletter, mailing, 1)
n.send_mailings()
'''


class PostcardSubscriber(models.Model):
    """ A model for grabbing addresses to send postcards """
    date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False)
    date_updated = models.DateTimeField(blank=True, auto_now=True, editable=False)
    address = models.TextField()
    name = models.CharField(max_length=120)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = _('Postcard Subscriber')


def send_notification_email(newsletter, message, instance):
    recipient_list = ['sng@luxagraf.net',]
    subject = _('[%(site)s] New Subscriber to "%(object)s"') % {
        'site': Site.objects.get_current().name,
        'object': newsletter,
    }
    send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list, fail_silently=True)


@receiver(post_save, sender=PostcardSubscriber)
def post_save_events(sender, update_fields, created, instance, **kwargs):
    message = "%s has requested a postcard.\nSend to:\n%s" %(instance.name, instance.address)
    send_notification_email("Postcards", message, instance)


@receiver(post_save, sender=Subscriber)
def post_save_events(sender, update_fields, created, instance, **kwargs):
    if instance.subscribed: 
        message = "%s has signed up for %s." %(instance.email_field, instance.newsletter)
        send_notification_email(instance.newsletter, message, instance)