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

import os.path
import re
from datetime import datetime

from croniter import croniter
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.shortcuts import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from rest_framework.serializers import ValidationError

try:
    from urlparse import urljoin
except ImportError:
    from urllib.parse import urljoin


def validate_special_characters(value):
    unnecessary_special_characters = [u"“", u"”", u"’", u"–"]
    errors = []

    for character in value:
        if character in unnecessary_special_characters:
            errors.append(character)

    if errors:
        raise ValidationError(
            u'Text "{0}" has special characters which can be replaced: {1}'.format(
                value, " ".join(errors)
            )
        )


@python_2_unicode_compatible
class Schedule(models.Model):

    """
    Schedules (sometimes referred to as Protocols) are the method used to
    define the rate and frequency at which the messages are sent to
    the recipient
    """

    minute = models.CharField(_("minute"), max_length=64, default="*")
    hour = models.CharField(_("hour"), max_length=64, default="*")
    day_of_week = models.CharField(_("day of week"), max_length=64, default="*")
    day_of_month = models.CharField(_("day of month"), max_length=64, default="*")
    month_of_year = models.CharField(_("month of year"), max_length=64, default="*")
    scheduler_schedule_id = models.UUIDField(
        _("scheduler schedule id"), null=True, blank=True, default=None
    )

    class Meta:
        verbose_name = _("schedule")
        verbose_name_plural = _("schedules")
        ordering = ["month_of_year", "day_of_month", "day_of_week", "hour", "minute"]

    @property
    def cron_string(self):
        return "{minute} {hour} {dom} {moy} {dow}".format(
            minute=self.rfield(self.minute),
            hour=self.rfield(self.hour),
            dom=self.rfield(self.day_of_month),
            moy=self.rfield(self.month_of_year),
            dow=self.rfield(self.day_of_week),
        )

    def rfield(self, s):
        return s and str(s).replace(" ", "") or "*"

    def __str__(self):
        return "{0} {1} {2} {3} {4} (m/h/d/dM/MY)".format(
            self.rfield(self.minute),
            self.rfield(self.hour),
            self.rfield(self.day_of_week),
            self.rfield(self.day_of_month),
            self.rfield(self.month_of_year),
        )

    @property
    def send_url(self):
        """
        Returns the URL to send this schedule's subscriptions
        """
        callback_path = reverse("schedule-detail", args=[str(self.id)])
        callback_path = "{}send/".format(callback_path)
        return urljoin(settings.STAGE_BASED_MESSAGING_URL, callback_path)

    @property
    def scheduler_format(self):
        """
        Returns the format that the scheduler service is expecting.
        """
        return {
            "frequency": None,
            "cron_definition": self.cron_string,
            "endpoint": self.send_url,
            "auth_token": settings.SCHEDULER_INBOUND_API_TOKEN,
        }

    def get_run_times_between(self, start, end):
        """Gets a list of datetimes for when this cron schedule would be
        run between the given start and end datetimes.
        """
        dates = []
        for dt in croniter(self.cron_string, start, ret_type=datetime):
            if dt > end:
                break
            dates.append(dt)
        return dates


@python_2_unicode_compatible
class MessageSet(models.Model):

    """
        Details about a set of messages that a recipient can be sent on
        a particular schedule
    """

    CONTENT_TYPES = (("text", "Text"), ("audio", "Audio"))

    short_name = models.CharField(_("Short name"), max_length=100, unique=True)
    label = models.CharField(
        _("User-readable name"), max_length=100, blank=True, default=""
    )
    notes = models.TextField(_("Notes"), null=True, blank=True)
    channel = models.CharField(_("Channel"), max_length=64, null=True, blank=True)
    next_set = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.SET_NULL
    )
    default_schedule = models.ForeignKey(
        Schedule, related_name="message_sets", null=False, on_delete=models.PROTECT
    )
    content_type = models.CharField(
        choices=CONTENT_TYPES, max_length=20, default="text"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "%s" % self.short_name

    def get_messageset_max(self, lang):
        return self.messages.filter(lang=lang).count()

    def get_all_run_dates(self, start, lang, schedule=None, initial=None):
        """Returns the complete list of dates this MessageSet would run on given
        a start datetime, language, Schedule and initial message. If no
        Schedule is passed it will use the configured default_schedule. If no
        Initial is passed it assumes the full set will run.
        """
        if schedule is None:
            schedule = self.default_schedule
        dates = []
        set_max = self.get_messageset_max(lang)
        iters = initial if initial else 1  # start iterator at initial message
        for dt in croniter(schedule.cron_string, start, ret_type=datetime):
            if iters > set_max:
                break
            iters = iters + 1
            dates.append(dt)
        return dates


def generate_new_filename(instance, filename):
    ext = os.path.splitext(filename)[-1]  # get file extension
    return "%s%s" % (datetime.now().strftime("%Y%m%d%H%M%S%f"), ext)


@python_2_unicode_compatible
class BinaryContent(models.Model):
    """
        File store for reference in messages. Storage method handle by
        settings file.
    """

    content = models.FileField(upload_to=generate_new_filename, max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "%s" % (self.content.path.split("/")[-1])


@python_2_unicode_compatible
class Message(models.Model):

    """
        Messages that a recipient can be sent
    """

    messageset = models.ForeignKey(
        MessageSet, related_name="messages", null=False, on_delete=models.CASCADE
    )
    sequence_number = models.IntegerField(null=False, blank=False)
    lang = models.CharField(max_length=6, null=False, blank=False)
    metadata = JSONField(default=dict)
    text_content = models.TextField(
        null=True, blank=True, validators=[validate_special_characters]
    )
    binary_content = models.ForeignKey(
        BinaryContent,
        related_name="message",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    WHATSAPP_INVALID_VALUE_PATTERN = re.compile(r"(\n|\t|\s{4})")

    class Meta:
        ordering = ["sequence_number"]
        unique_together = ("messageset", "sequence_number", "lang")

    def clean(self):
        # Don't allow messages to have neither a text or binary content
        if any([self.text_content, self.binary_content]) is False:
            raise ValidationError(_("Messages must have text or file attached"))
        # Check for WhatsApp disallowed characters
        if "whatsapp" in (
            self.messageset.channel or ""
        ).lower() and self.WHATSAPP_INVALID_VALUE_PATTERN.findall(self.text_content):
            raise ValidationError(
                _(
                    "Text for WhatsApp must not include newline, tabs, or 4 "
                    "consecutive spaces."
                )
            )

    def save(self, *args, **kwargs):
        self.clean()
        super(Message, self).save(*args, **kwargs)

    def __str__(self):
        return _("Message %s in %s from %s") % (
            self.sequence_number,
            self.lang,
            self.messageset.short_name,
        )
