import os

import django_comments as comments
from csp.decorators import csp_update
from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import HttpResponse, Http404
from django.utils.translation import ugettext as _
from django.views import generic

from . import models, forms


class WYSIWYGEditorMixin(PermissionRequiredMixin):
    """
    A view to update the Content-Security-Policy for the WYSIWYG editor.
    Since it is only used in edit forms, it extends the PermissionRequiredMixin.     
    """

    @csp_update(SCRIPT_SRC="'unsafe-eval'")
    def dispatch(self, request, *args, **kwargs):
        """Add "unsafe-eval" to the Content-Secuirty-Policy HTTP Headers for the
        WYSIWYG Editor.

        :param request: the HTTP Request Object
        :param args: args for the super dispatch
        :param kwargs: kwargs for the super dispatch
        :return: django HTTPResponse object
        """
        return super(WYSIWYGEditorMixin, self).dispatch(
            request, *args, **kwargs)


class ArticleArchiveMixin(object):
    """Mixin to add common context data to Views of the news archive."""
    category = None

    def get_category(self, queryset):
        """
        Filter the queryset by category if one has been specified in the URL

        :param queryset: an model.Article.objects Queryset
        :return: an model.Article.objects Queryset filterd by category
        """

        category_slug = self.kwargs.get('category')
        if not category_slug:
            self.category = None
            return queryset
        try:
            self.category = models.Category.objects.get(slug=category_slug)
        except models.Category.DoesNotExist:
            raise Http404(_("This Category does not exist."))
        return queryset.filter(category=self.category)

    def get_context_data(self, **kwargs):
        """
        Adds the categories and the active category to the template context.

        :return: an django.template.context
        """

        context = super(ArticleArchiveMixin, self).get_context_data(**kwargs)
        context['categories'] = models.Category.objects.all()
        context['active_category'] = self.category
        return context


class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
    """
    Displays the latest news and the filters to browse the archives.
    """

    queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
    date_field = 'date_created'
    paginate_by = 5
    context_object_name = 'article_list'
    allow_empty = True

    def get_queryset(self):
        """
        Filter the Queryset by category.

        :return: models.Article.objects queryset
        """
        return self.get_category(
            super(ArticleArchiveIndex, self).get_queryset()
        )


class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
    """
    Displays the Articles filterd by a specific year
    """

    queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
    date_field = 'date_created'
    paginate_by = 5
    year_format = '%Y'
    make_object_list = True
    allow_empty = True

    def get_queryset(self):
        """
        Filter the Queryset by category.

        :return: models.Article.objects queryset
        """
        return self.get_category(
            super(ArticleYearArchive, self).get_queryset()
        )


class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
    """
    Displays the Articles filterd by a specific month
    """

    queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
    date_field = 'date_created'
    month_format = '%m'
    paginate_by = 5
    make_object_list = True
    allow_empty = True

    def get_queryset(self):
        """
        Filter the Queryset by category.

        :return: models.Article.objects queryset
        """
        return self.get_category(
            super(ArticleMonthArchive, self).get_queryset()
        )


class ArticleDetail(generic.DetailView):
    """
    Render the news Article, but only if it got published.
    """

    queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)


class ArticleForm(WYSIWYGEditorMixin, generic.UpdateView):
    """
    View to add or edit an Article
    """
    model = models.Article
    form_class = forms.ArticleForm
    permission_required = 'content.change_article'

    def get_context_data(self, **kwargs):
        """Set the title variable to create/or edit article."""
        context = super(ArticleForm, self).get_context_data(**kwargs)
        context['title'] = _("Edit Article") if self.kwargs.get('pk') else \
            _("Create Article")
        return context

    def get_object(self, queryset=None):
        """Get the Article or create a new one if no id has been provided.

        :param queryset: Get the single item from this filtered queryset. 
        :return: 
        """
        queryset = queryset or self.get_queryset()
        if self.kwargs.get('pk'):
            return queryset.get(pk=self.kwargs['pk'])
        return models.Article(author=self.request.user)


class PageAddForm(WYSIWYGEditorMixin, generic.CreateView):
    """ Renders an Form to create a new page for users with conforming 
    permissions."""

    form_class = forms.PageForm
    template_name = 'content/page_form.html'
    permission_required = 'content.add_page'

    def get_initial(self):
        """Try to get the path of the parent page as initial data."""
        path = os.path.splitext(self.kwargs['path'])[0]
        if path.startswith('/'):
            path = path[1:]
        if path.endswith('/'):
            path = path[:-1]
        parent = models.Page.objects.get(path=path)
        return {'parent': parent}


class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView):
    """Renders an Form to edit a page for users with conforming permissions."""

    form_class = forms.PageForm
    model = models.Page
    permission_required = 'content.change_page'

    def get_object(self, queryset=None):
        """ Get the path from the URL and fetch the corresponding page.
         
        First get the path wihout fileextentsion leading or trailing slashes,
        then search in the database if such a page exists.

        :param queryset: Get the single item from this filtered queryset.
        :return: 
        """
        path = os.path.splitext(self.kwargs['path'])[0]
        queryset = queryset or self.get_queryset()
        if path.startswith('/'):
            path = path[1:]
        if path.endswith('/'):
            path = path[:-1]
        return queryset.get(path=path)


class PageHtml(generic.DetailView):
    """Display static HTML content from the database."""

    model = models.Page

    def get_object(self, queryset=None):
        """Get the page content from the db that equals the given URL.

        :param queryset: Get the single item from this filtered queryset.
        :return: 
        """
        queryset = queryset or self.get_queryset()
        try:
            return queryset.get(path=self.kwargs['path'],
                                content_type=models.HTML_PAGE)
        except models.Page.DoesNotExist:
            raise Http404(
                _("No Page found matching the Path %s") % self.request.path
            )

    def get_template_names(self):
        """Each static page can define its own template, so return the template
        name that has been stored in the dataset.

        :return: filename of the template to render.
        """
        return self.object.template


class PagePdf(generic.DeleteView):
    """Deliver an static PDF File under this given URL."""

    model = models.Page

    def get_object(self, queryset=None):
        """Get the PDF page from the db that equals the given URL. 

        :param queryset: Get the single item from this filtered queryset.
        :return: models.Page object or raise a 404 if not found. 
        """
        queryset = queryset or self.get_queryset()
        try:
            return queryset.get(path=self.kwargs['path'],
                                content_type=models.PDF_PAGE)
        except models.Page.DoesNotExist:
            raise Http404(
                _("No PDF Document found matching the Path %s") %
                self.request.path
            )

    def render_to_response(self, context, **response_kwargs):
        """Stream the PDF File to the client and set the right content headers.

        :param context: useless only for compatility
        :param response_kwargs: will be added to the HttpResponse kwargs.
        :return: an HTTPResponse with PDF Content or an Http404 exception
        """
        try:
            with open(self.object.pdf_file.path, 'rb') as pdf_file:
                response = HttpResponse(
                    content=pdf_file.read(),
                    content_type='application/pdf',
                    **response_kwargs
                )
                return response
        except:
            raise Http404('File not Found %s.pdf' % self.kwargs['path'])


class StartPage(generic.TemplateView):
    """The Frontpage, a page with the latest infos and some static content."""
    template_name = 'index.html'

    def get_context_data(self):
        """ Adds recent ariticles and recent comments to the context.
        
        :return: array() with the context data 
        """
        page = models.Page.objects.get(slug='index')
        recent_comment_list = comments.get_model().objects.filter(
            site__pk=settings.SITE_ID,
            is_public=True,
            is_removed=False,
        )
        recent_comment_list = recent_comment_list.order_by('-submit_date')[:10]
        context = {
            'title': page.title,
            'content': page.content,
            'recent_article_list': models.Article.objects.published()[:3],
            'recent_comment_list': recent_comment_list,
        }
        return context

    queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
