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

from __future__ import unicode_literals

import sys
from datetime import datetime

from django.core.urlresolvers import reverse
from django.db import models
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
from django.utils import six
from django.utils.functional import lazy
from django.utils.safestring import mark_safe

mark_safe_lazy = lazy(mark_safe, six.text_type)
newsbox_models = []


def newsbox_mcls_processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts):
    if 'newsbox_title' not in class_attrs:
        class_attrs['newsbox_title'] = models.CharField(
            verbose_name=_('title'),
            help_text=_('The title should not be too long and should be explicit'),
            max_length=255)
        newsbox_opts['trans_fieldnames'].append('newsbox_title')
    if 'newsbox_slug' not in class_attrs:
        class_attrs['newsbox_slug'] = models.SlugField(
            verbose_name=_('slug'),
            help_text=_('The part of the title that is used in the URL'))
        newsbox_opts['trans_fieldnames'].append('newsbox_slug')
    if 'newsbox_summary' not in class_attrs:
        class_attrs['newsbox_summary'] = models.TextField(
            verbose_name=_('summary'),
            help_text=_('This text must be short, it will be displayed in the lists.'))
        newsbox_opts['trans_fieldnames'].append('newsbox_summary')
    if 'newsbox_body' not in class_attrs:
        class_attrs['newsbox_body'] = models.TextField(
            verbose_name=_('body'),
            null=False, blank=True)
        newsbox_opts['trans_fieldnames'].append('newsbox_body')


def newsboxseo_mcls_processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts):
    class_attrs['newsbox_meta_description'] = models.TextField(
        verbose_name=_('meta description'),
        help_text=mark_safe_lazy(
            _('The meta-description allow to improve your referencing and indicates to '
              'search engines a summary of your page. To draft her better, you can consult '
              '%(help_link)sthis help page%(link_end)s.') % {
                'help_link': _(
                    # Translators: This is the "help_link" URL for the meta-description help
                    '%(start_tag)s'
                    'https://support.google.com/webmasters/answer/35624?rd=1&hl=en#1'
                    '%(end_tag)s') % {'start_tag': '<a target="_blank" href="',
                                      'end_tag': '">'},
                'link_end': '</a>'}
        ),
        max_length=255, blank=True, null=True)
    class_attrs['newsbox_meta_keywords'] = models.CharField(
        verbose_name=_('meta keywords'),
        help_text=_('List of few keywords (4-5) separated by commas. These words must be relevant '
                    'with the contents of your page : they must be present in the contents of the '
                    'page where they should be put in \"bold\" to highlight their importance. '
                    'Also try to place them in the title of your page. Note that even if Google '
                    'does not use this meta anymore, other search engines still use these, like '
                    'Yandex, the most used search engine in Russia.'),
        max_length=255, blank=True, null=True)
    class_attrs['newsbox_canonical_url'] = models.URLField(
        verbose_name=_('canonical URL'),
        help_text=mark_safe_lazy(
            _('This field allows you to indicate to search engines the "official" URL of a '
              'duplicated contents. If your contents come from another page (of your website '
              'or another one), then you should indicate the URL of the "source" to avoid '
              '"duplicated contents" penalization of search engines. You can consult '
              '%(help_link_1)sthe moz dossier%(end_link)s on this matter or '
              '%(help_link_2)sGoogle help%(end_link)s.') % {
                'help_link_1': _(
                    # Translators: This is the "help_link_1" URL for the canonical URL help
                    '%(start_tag)s'
                    'https://moz.com/blog/canonical-url-tag-the-most-important-advancement-in-seo-practices-since-sitemaps'  # noqa
                    '%(end_tag)s') % {'start_tag': '<a target="_blank" href="',
                                      'end_tag': '">'},
                'help_link_2': _(
                    # Translators: This is the "help_link_2" URL for the canonical URL help
                    '%(start_tag)s'
                    'https://support.google.com/webmasters/answer/139066?hl=en#2'
                    '%(end_tag)s') % {'start_tag': '<a target="_blank" href="',
                                      'end_tag': '">'},
                'end_link': '</a>'}
        ),
        null=True, blank=True,)

    newsbox_opts['trans_fieldnames'] += [
        'newsbox_meta_description', 'newsbox_meta_keywords', 'newsbox_canonical_url']


###################################################################################################
###################################################################################################
class NewsboxManager(models.Manager):
    """
    Filter published news
    """

    def published(self):
        q = self.model.q_published()
        return super(NewsboxManager, self).get_queryset().filter(q)


###################################################################################################
###################################################################################################
class NewsboxModelBase(models.base.ModelBase):
    """
    Metaclass used to inject django-newsboxfunctionalities into django
    orm.
    """

    def __new__(mcls, class_name, class_bases, class_attrs):
        super_new = super(NewsboxModelBase, mcls).__new__

        # six.with_metaclass() inserts an extra class called 'NewBase' in the
        # inheritance tree: Model -> NewBase -> object. But the initialization
        # should be executed only once for a given model class.

        # attrs will never be empty for classes declared in the standard way
        # (ie. with the `class` keyword). This is quite robust.
        if class_name == 'NewBase' and class_attrs == {}:
            return super_new(mcls, class_name, class_bases, class_attrs)

        # Also ensure initialization is only performed for subclasses of Model
        # (excluding Model class itself).
        # parents = [b for b in class_bases
        #            if isinstance(b, NewsboxModelBase) and
        #            not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))]

        # retrieve newsbox options in Meta class and delete these from the
        # Meta class to avoid django complain about unsupported keys.
        # We register our meta options in `_newsbox_meta`
        attr_meta = class_attrs.get('Meta', None)
        if attr_meta and not getattr(attr_meta, 'ordering', None):
            class_attrs['Meta'].ordering = ('-newsbox_publication_start_date',)
        abstract = getattr(attr_meta, 'abstract', False)
        newsbox_opts = {}
        newsbox_opts_keys = [
            'detail_url_name',
            'list_url_name',
            'metaclass_base_processor',
            'metaclass_final_processor',
            'trans_fieldnames']
        for opt in newsbox_opts_keys:
            newsbox_opt = 'newsbox_' + opt
            if hasattr(attr_meta, newsbox_opt):
                newsbox_opts[opt] = getattr(attr_meta, newsbox_opt)
                delattr(attr_meta, newsbox_opt)

        if 'trans_fieldnames' not in newsbox_opts:
            newsbox_opts['trans_fieldnames'] = []

        # register newsbox_objects manager
        class_attrs['newsbox_objects'] = NewsboxManager()

        # Call specific metaclass processor of all bases
        if not abstract:
            for base in class_bases:
                try:
                    processor = base._newsbox_meta['metaclass_base_processor']
                except (AttributeError, KeyError):
                    continue
                processor = getattr(sys.modules[base.__module__], processor)
                processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts)

            for base in class_bases:
                try:
                    processor = base._newsbox_meta['metaclass_final_processor']
                except (AttributeError, KeyError):
                    continue
                processor = getattr(sys.modules[base.__module__], processor)
                processor(mcls, class_name, class_bases, class_attrs, base, newsbox_opts)

        if 'default_manager' not in class_attrs:
            class_attrs['default_manager'] = class_attrs['newsbox_objects']
        if 'objects' not in class_attrs:
            class_attrs['objects'] = class_attrs['newsbox_objects']
        # contructs class
        cls = super_new(mcls, class_name, class_bases, class_attrs)

        # Add the detail_url_name to _newsbox_meta. if not defined, we build the
        # default one with this format : <app_label>:<model_name>_detail
        # Names ar lowered.
        if not abstract:
            if 'detail_url_name' not in newsbox_opts:
                newsbox_opts['detail_url_name'] = '%s:%s_detail' % (cls._meta.app_label.lower(),
                                                                    cls._meta.model_name.lower())
            if 'list_url_name' not in newsbox_opts:
                newsbox_opts['list_url_name'] = '%s:%s_list' % (cls._meta.app_label.lower(),
                                                                cls._meta.model_name.lower())
            newsbox_models.append(cls)
        cls.add_to_class('_newsbox_meta', newsbox_opts)
        return cls


###################################################################################################
###################################################################################################
@python_2_unicode_compatible
class NewsboxBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Abstract class to build your own news
    """

    STATUS_DRAFT = 'draft'
    STATUS_PUBLISHED = 'published'
    STATUS_ARCHIVED = 'archived'
    STATUS_CHOICES = (
        (STATUS_DRAFT, _('draft')),
        (STATUS_PUBLISHED, _('published')),
        (STATUS_ARCHIVED, _('archived')),
    )

    # Automatic fields
    newsbox_creation_date = models.DateTimeField(
        verbose_name=_('creation date'),
        auto_now_add=True,
        editable=False)
    newsbox_changed_date = models.DateTimeField(
        verbose_name=_('last update date'),
        auto_now=True,
        editable=False)

    # event datetime
    newsbox_date = models.DateTimeField(
        verbose_name=_('datetime'),
        help_text=_('Datetime of the event related to this news'),
        default=None, db_index=False,
        null=True, blank=True,
    )

    # Basic Publication Fields
    newsbox_publication_start_date = models.DateTimeField(
        verbose_name=_('publication start date'),
        default=datetime.now, db_index=True,
        help_text=_(
            'When the news should go live. '
            'Status must be "Published" for news to go live.'),
        null=False, blank=False)
    newsbox_status = models.CharField(
        verbose_name=_('status'), max_length=10,
        choices=STATUS_CHOICES, default=STATUS_DRAFT,
        help_text=_('Use "Draft" to prepare your content before publishing and "Archived" to '
                    'unpublish content without deleting it'))

    default_manager = NewsboxManager()
    objects = default_manager

    class Meta:
        abstract = True
        verbose_name = _('news')
        verbose_name_plural = _('news')
        ordering = ('-newsbox_publication_start_date', )
        newsbox_metaclass_base_processor = 'newsbox_mcls_processor'
        permissions = (
            ('publish', _('Can publish')),
        )

    @classmethod
    def q_published(cls):
        # Only published News
        q = models.Q(newsbox_status=cls.STATUS_PUBLISHED)
        # exclude news with publication start date in the future
        q &= models.Q(newsbox_publication_start_date__lte=now())
        return q

    @classmethod
    def _has_perm(cls, action, user, obj=None):
        if user.is_superuser:
            return True
        return user.has_perm('%s.%s_%s' % (cls._meta.app_label, action, cls._meta.model_name), obj)

    @classmethod
    def has_publish_permission(cls, user, obj=None):
        return cls._has_perm('publish', user, obj) or cls._has_perm('change', user, obj)

    @classmethod
    def has_change_permission(cls, user, obj=None):
        return cls._has_perm('change', user, obj)

    def __str__(self):
        return six.text_type(self.newsbox_title)

    @property
    def is_published(self):
        if self.newsbox_status != self.STATUS_PUBLISHED:
            return False
        return now() >= self.newsbox_publication_start_date

    def publish(self, commit=True):
        updated = False
        if self.newsbox_status != self.STATUS_PUBLISHED:
            self.newsbox_status = self.STATUS_PUBLISHED
            updated = True
        if self.newsbox_publication_start_date:
            now_datetime = now()
            if now_datetime < self.newsbox_publication_start_date:
                self.newsbox_publication_start_date = now_datetime
                updated = True
        if updated and commit:
            self.save()
        return updated

    def unpublish(self, commit=True):
        updated = False
        if self.newsbox_status == self.STATUS_PUBLISHED:
            self.newsbox_status = self.STATUS_ARCHIVED
            updated = True
        if updated and commit:
            self.save()
        return updated

    def get_slug(self, *args, **kwargs):
        return self.newsbox_slug

    def get_absolute_url(self, *args, **kwargs):
        return reverse(self._newsbox_meta['detail_url_name'], args=(
            self.newsbox_publication_start_date.strftime("%Y"),
            self.newsbox_publication_start_date.strftime("%m"),
            self.newsbox_publication_start_date.strftime("%d"),
            self.get_slug(),))

    def save(self, *args, **kwargs):
        # be sure to have a publication start date
        if not self.newsbox_publication_start_date:
            self.newsbox_publication_start_date = self.newsbox_creation_date
        super(NewsboxBase, self).save(*args, **kwargs)


###################################################################################################
###################################################################################################
class NewsboxExpiredBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Define News which expires in time and will not be displayed in front
    """
    newsbox_publication_end_date = models.DateTimeField(
        verbose_name=_('publication end date'),
        null=True, blank=True, db_index=True,
        help_text=_('When the news will expire. Leave empty to never expire.'))

    class Meta:
        abstract = True

    @classmethod
    def q_published(cls):
        q = super(NewsboxExpiredBase, cls).q_published()
        # exclude news which are obsolete
        q &= (models.Q(newsbox_publication_end_date__gt=now()) |
              models.Q(newsbox_publication_end_date__isnull=True))
        return q

    @property
    def is_published(self):
        if not super(NewsboxExpiredBase, self).is_published:
            return False
        return not self.newsbox_publication_end_date or now() <= self.newsbox_publication_end_date

    def publish(self, commit=True):
        updated = super(NewsboxExpiredBase, self).publish(commit=False)
        if self.newsbox_publication_end_date:
            if now() > self.newsbox_publication_end_date:
                self.newsbox_publication_end_date = None
                updated = True
        if updated and commit:
            self.save()
        return updated


###################################################################################################
###################################################################################################
class NewsboxSEOBase(six.with_metaclass(NewsboxModelBase, models.Model)):
    """
    Define News which have SEO fields
    """

    # SEO Fields
    newsbox_indexed = models.PositiveSmallIntegerField(
        verbose_name=_('indexed'),
        default=1,
        choices=((0, _('Not indexed on search engines'), ),
                 (1, _('Indexed on search engines'), )),
        help_text=_('An unindexed news will not be indexed by search engines.'))

    class Meta:
        abstract = True
        newsbox_metaclass_base_processor = 'newsboxseo_mcls_processor'

    def get_meta_informations(self):
        if not hasattr(self, '_meta_informations'):
            self._meta_informations = {
                'description': self.newsbox_meta_description,
                'keywords': self.newsbox_meta_keywords,
                'robots': [],
                'title': '%s' % self,
            }
            if not self.newsbox_indexed:
                self._meta_informations['robots'].append('noindex')
            if self.newsbox_meta_description:
                self._meta_informations['robots'].append('noodp')
        return self._meta_informations