import json

from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse
from django.views.generic import TemplateView

from bots.forms import BotForm
from bots.models import TelegramBot
from cabinet.utils import CabinetViewMixin
from config.utils import get_config_form


class BaseBotConfigView(CabinetViewMixin, TemplateView):
    template_name = 'cabinet/bots/bot_form.html'
    context_object_name = 'feed'

    def get_queryset(self):
        return TelegramBot.objects.filter(owner=self.request.user)

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return self.render_to_response(self.get_context_data())

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        bot_form, config_form = self.get_forms()
        if bot_form.is_valid() and config_form.is_valid():
            return self.form_valid(bot_form, config_form)
        else:
            context = self.get_context_data(
                forms=(bot_form, config_form)
            )
            return self.render_to_response(context)

    def get_forms(self):
        bot = self.get_object()
        data = self.request.POST if self.request.method == 'POST' else None
        files = self.request.FILES if self.request.method == 'POST' else None
        return BotForm(data=data, files=files, instance=bot, module=self.get_content_type().model_class()), \
               get_config_form(self.get_content_type().model_class())(data=data, files=files,
                                                                      instance=bot.config if bot else None)

    def get_context_data(self, forms=None, **kwargs):
        ctx = super(BaseBotConfigView, self).get_context_data(**kwargs)
        ctx['bot_form'], ctx['config_form'] = self.get_forms() if forms is None else forms
        ctx['bot_module'] = model = self.model
        if hasattr(model, 'form_context_data'):
            bot = self.get_object()
            ctx.update(model.form_context_data(self, bot.config if bot else None))
        return ctx

    @property
    def model(self):
        return self.get_content_type().model_class()


# django-braces
class AjaxResponseMixin(object):
    """
    Mixin allows you to define alternative methods for ajax requests. Similar
    to the normal get, post, and put methods, you can use get_ajax, post_ajax,
    and put_ajax.
    """
    def dispatch(self, request, *args, **kwargs):
        request_method = request.method.lower()

        if request.accepts("application/json") and request_method in self.http_method_names:
            handler = getattr(self, "{0}_ajax".format(request_method),
                              self.http_method_not_allowed)
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return handler(request, *args, **kwargs)

        return super(AjaxResponseMixin, self).dispatch(
            request, *args, **kwargs)

    def get_ajax(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def post_ajax(self, request, *args, **kwargs):
        return self.post(request, *args, **kwargs)

    def put_ajax(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def delete_ajax(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)


class JSONResponseMixin(object):
    """
    A mixin that allows you to easily serialize simple data such as a dict or
    Django models.
    """
    content_type = None
    json_dumps_kwargs = None
    json_encoder_class = DjangoJSONEncoder

    def get_content_type(self):
        if self.content_type is not None and not isinstance(self.content_type, str):
            raise ImproperlyConfigured(
                '{0} is missing a content type. Define {0}.content_type, '
                'or override {0}.get_content_type().'.format(
                    self.__class__.__name__))
        return self.content_type or "application/json"

    def get_json_dumps_kwargs(self):
        if self.json_dumps_kwargs is None:
            self.json_dumps_kwargs = {}
        self.json_dumps_kwargs.setdefault('ensure_ascii', False)
        return self.json_dumps_kwargs

    def render_json_response(self, context_dict, status=200):
        """
        Limited serialization for shipping plain data. Do not use for models
        or other complex or custom objects.
        """
        json_context = json.dumps(
            context_dict,
            cls=self.json_encoder_class,
            **self.get_json_dumps_kwargs()).encode('utf-8')
        return HttpResponse(json_context,
                            content_type=self.get_content_type(),
                            status=status)

    def render_json_object_response(self, objects, **kwargs):
        """
        Serializes objects using Django's builtin JSON serializer. Additional
        kwargs can be used the same way for django.core.serializers.serialize.
        """
        json_data = serializers.serialize("json", objects, **kwargs)
        return HttpResponse(json_data, content_type=self.get_content_type())
