# coding=utf-8
import json

from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.serializers.json import DjangoJSONEncoder
from django.http.response import Http404, HttpResponse
from django.template import TemplateDoesNotExist, TemplateSyntaxError
from django.views.decorators.csrf import csrf_protect

from .models import Content
from .utils import (NAMESPACE_DELIMITER, can_edit_content, dum_request,
                    parse_cms_template)
from .validators import validate_and_get_template

_template_not_defined = object()


def render(request, template_name=_template_not_defined,
           template_context=None, content_type=None, status=None,
           using=None, cms_context=None):
    """
    If request is given:
    Checks if an entry exists in the Content model
    for the request path and renders it.
    If it fails with 404 and a template_name is given,
    then it falls back to render_to_response with the given arguments.

    If request is not passed:
    render_to_response is called with the given arguments.

    If render_to_response fails with ValueError or Http404,
     then it will be rendered without cms_context.
    """
    if request is not None:
        try:
            cms_page = Content.objects.get(url=request.path_info)
        except Content.DoesNotExist:
            pass
        else:
            try:
                return render_content(
                    cms_page=cms_page,
                    request=request,
                    template_context=template_context,
                    content_type=content_type,
                    status=status,
                    using=using
                )
            except (TemplateDoesNotExist, Http404):
                if settings.DEBUG:
                    raise

    if template_name is _template_not_defined:
        raise Http404('Backup template name is not defined')

    try:
        return render_to_response(
            template_name=template_name,
            template_context=template_context,
            content_type=content_type,
            status=status,
            using=using,
            request=request,
            cms_context=cms_context
        )
    except (ValueError, Http404):
        if settings.DEBUG:
            raise
        else:
            return render_to_response(
                template_name=template_name,
                template_context=template_context,
                content_type=content_type,
                status=status,
                using=using,
                request=request
            )


def render_to_response(template_name,
                       template_context=None, content_type=None, status=None,
                       using=None, request=None, cms_context=None, public=True):

    """
    This is similar to the django's render_to_response
    and accepts all of it's non deprecated arguments.
    It also takes two additional parameters request and cms_context.

    It will render the given template with given template_context, cms_context
    and returns corresponding response
    or raises Http404 if the parsing fails for the given cms_context.

    :rtype: HttpResponse

    :return: :raise ValueError:
    """
    @csrf_protect
    def _render(_request):
        template = validate_and_get_template(template_name, using=using)
        content = template.render(context=template_context, request=_request)

        if cms_context is not None:
            if isinstance(cms_context, dict):
                try:
                    content = parse_cms_template(
                        html=content, dictionary=cms_context,
                        public=public, request=_request
                    )
                except (ValidationError, TemplateSyntaxError) as e:
                    raise Http404(e)
            else:
                raise ValueError('cms_context should be an instance of dict')

        return HttpResponse(content, content_type, status)

    # if you wish to know why the view
    # or other render functions are not decorated with csrf_protect
    # read the comment in django.contrib.flatpages.views
    if request is None:
        # since we are using csrf_token decorator
        # it expects the first argument of the function to be a request object
        # and we use this dum_request to render the template
        # instead of passing None
        # so as to load the csrf_token inside the template
        request = dum_request
    return _render(request)


def render_content(cms_page,
                   request=None, template_context=None,
                   content_type=None, status=None, using=None):
    """
    Renders a given Content object and returns the HttpResponse
    or will raise Http404 if any syntax or validation errors occur.

    The response object is processed with auth_data or public_data according to the given request.

    Since we are passing an instance of Content object, this function might raise TemplateDoesNotExist
    for the template associated with the Content instance.

    :type cms_page: Content

    :type request: HttpRequest | None

    :type template_context: dict | None

    :rtype: HttpResponse

    :return: :raise Http404:
    """
    template_context = template_context or {}
    template_context['cms_content'] = cms_page

    if request is not None and can_edit_content(request.user):
        editor_context = {
            'view': 'author',
            'cms_data': json.dumps(cms_page.auth_data),
            'cms_page_id': cms_page.id,
            'modified_on': json.dumps(
                cms_page.modified_on, cls=DjangoJSONEncoder
            ),
            'namespace_delimiter': json.dumps(NAMESPACE_DELIMITER)
        }

        cache.set(
            key='template_context_{}'.format(cms_page.id),
            value=template_context,
            timeout=24 * 60 * 60
        )
        template_context.update(editor_context)

        return render_to_response(
            template_name=cms_page.template,
            template_context=template_context,
            content_type=content_type,
            status=status,
            using=using,
            request=request,
            cms_context=cms_page.auth_data,
            public=False
        )

    if cms_page.is_public:
        return render_to_response(
            template_name=cms_page.template,
            template_context=template_context,
            content_type=content_type,
            status=status,
            using=using,
            request=request,
            cms_context=cms_page.public_data,
            public=True
        )

    raise Http404('Page not found')
