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

from __future__ import unicode_literals

import logging
from importlib import import_module

from django.contrib.contenttypes.fields import GenericRelation
from django.core.cache import caches
from django.db import models
from django.db.models import DateTimeField
from django.utils.encoding import python_2_unicode_compatible
from django.utils.module_loading import import_string
from django.utils.text import slugify
from django.utils.translation import ugettext, ugettext_lazy as _
from first import first
from touchtechnology.admin.mixins import AdminUrlMixin
from touchtechnology.common.db.models import BooleanField, HTMLField, TemplatePathField
from touchtechnology.content.app_settings import (
    NODE_CACHE, PAGE_CONTENT_CLASSES, PAGE_TEMPLATE_BASE, PAGE_TEMPLATE_FOLDER, PAGE_TEMPLATE_REGEX,
)

logger = logging.getLogger(__name__)

PAGE_CONTENT_CLASS_CHOICES = zip(PAGE_CONTENT_CLASSES,
                                 PAGE_CONTENT_CLASSES)

PAGE_CONTENT_CLASS_DEFAULT = first(PAGE_CONTENT_CLASSES)

SITE_CACHE_KEY = '_site_cache'


class AdminUrlModel(AdminUrlMixin, models.Model):

    class Meta:
        abstract = True

    def _get_admin_namespace(self):
        return 'admin:content:%s' % self._meta.model_name

    def _get_url_args(self):
        return (self.id,)

    def __repr__(self):
        return '<{}: {}>'.format(self.__class__, self)


class Page(models.Model):

    # allows us to customise the template on a per page basis

    template = TemplatePathField(max_length=200, blank=True,
                                 template_base=PAGE_TEMPLATE_BASE,
                                 template_folder=PAGE_TEMPLATE_FOLDER,
                                 match=PAGE_TEMPLATE_REGEX,
                                 recursive=True,
                                 verbose_name=_("Template"))

    # SEO metadata fields

    keywords = models.CharField(
        max_length=255, blank=True, verbose_name=_("Keywords"),
        help_text=_("This should be a comma-separated list of terms that "
                    "indicate the content of this page - used to assist "
                    "Search Engines rank your page."))

    description = models.CharField(
        max_length=255, blank=True, verbose_name=_("Description"),
        help_text=_("Search Engines may use this when determining the "
                    "relevance of your page."))

    last_modified = DateTimeField(auto_now=True)

    def __repr__(self):
        return '<Page: #{}>'.format(self.pk)


class Content(models.Model):

    # FIXME and put the ``copy`` attribute on the ``PageContent`` model, we
    # aren't actually reusing the ``Content`` in any way, so simplify the
    # implementation to move the data back up and onto.

    # The M2M relation on ``Page`` will become a FK on ``PageContent``
    # instead.

    copy = HTMLField(blank=True)

    def __repr__(self):
        return '<Content: #%d>' % self.pk


class PageContent(models.Model):

    page = models.ForeignKey(
        Page, related_name='content', verbose_name=_("Page"),
        on_delete=models.PROTECT)
    label = models.SlugField(
        max_length=20, choices=PAGE_CONTENT_CLASS_CHOICES,
        default=PAGE_CONTENT_CLASS_DEFAULT, verbose_name=_("CSS class"))
    sequence = models.PositiveIntegerField(verbose_name=_("Sequence"))
    copy = HTMLField(blank=True, verbose_name=_("Copy"))

    last_modified = DateTimeField(auto_now=True)

    class Meta:
        ordering = ('sequence',)

    def __repr__(self):
        return '<PageContent: #%d, page=%d>' % (self.pk, self.page.pk)


@python_2_unicode_compatible
class Chunk(AdminUrlModel):

    slug = models.SlugField(verbose_name=_("Slug"))
    copy = HTMLField(blank=True, verbose_name=_("Page Copy"))

    def __str__(self):
        return self.slug


class NodeContent(models.Model):

    node = models.ForeignKey(
        'common.SitemapNode', related_name='contents', verbose_name=_("Node"),
        on_delete=models.PROTECT)
    copy = HTMLField(blank=True, verbose_name=_("Page Copy"))


@python_2_unicode_compatible
class Placeholder(models.Model):

    path = models.CharField(max_length=255, verbose_name=_("Module path"))
    namespace = models.CharField(
        max_length=255, db_index=True, verbose_name=_("Namespace"))

    nodes = GenericRelation(
        'common.SitemapNode', verbose_name=_("Nodes"))

    class Meta:
        ordering = ('path',)

    def __str__(self):
        try:
            cls = import_string(self.path)
        except (ImportError, ValueError):
            return ugettext('Not installed')
        return cls.verbose_name()

    def _install(self, parent=None):
        """
        Adding an internal method to allow us to rig up all the applications.
        """
        if not self.nodes.count():
            module_path, site = self.path.rsplit('.', 1)
            module = import_module(module_path)
            if hasattr(module, site):
                instance = getattr(module, site)
                self.nodes.create(
                    title=instance.name.title(),
                    slug=slugify(instance.name),
                    parent=parent,
                )

    @property
    def module(self):
        if not hasattr(self, '_module'):
            module_path = self.path.split('.')
            while module_path:
                try:
                    module = import_module('.'.join(module_path))
                    if hasattr(module, 'VERSION'):
                        self._module = module
                        break
                except ImportError:
                    module = None
                module_path.pop()
        return self._module

    def callable(self):
        try:
            return import_string(self.path)
        except ValueError:
            return None

    def site(self, node):
        cache_key = "node%d" % node.pk
        cls = import_string(self.path)
        site = cls(node=node, name=cache_key, **node.kwargs)
        return site

    def invalidate_cache(self):
        # Since this is forced cache invalidation, we should log it.
        logger.info("Forced invalidation of Application cache.")
        cache = caches[NODE_CACHE]
        return cache.clear()
    invalidate_cache.alters_data = True


@python_2_unicode_compatible
class Redirect(AdminUrlModel):

    source_url = models.CharField(
        max_length=250, verbose_name=_("Source URL"),
        help_text=_("The path that will trigger the redirection."))

    destination_url = models.CharField(
        max_length=500, verbose_name=_("Destination URL"),
        help_text=_("The URL or path that the browser will be sent to."))

    label = models.CharField(
        max_length=100, blank=True, verbose_name=_("Label"))

    active = BooleanField(default=True, verbose_name=_("Active"))
    permanent = BooleanField(default=False, verbose_name=_("Permanent"))

    class Meta:
        ordering = ('destination_url',)

    def __str__(self):
        if self.label:
            return self.label
        return '{src} → {dst}'.format(
            src=self.source_url, dst=self.destination_url)
