# -*- coding: utf-8 -*-
"""
Django REST Framework like viewset mixins for common model sqlalchemy actions
"""
from __future__ import absolute_import, print_function, unicode_literals

from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
from django.views.generic.edit import FormMixin

from ..db import meta
from ..forms import modelform_factory
from ..views.base import BaseMultipleObjectMixin, BaseSingleObjectMixin


class ListModelMixin(BaseMultipleObjectMixin):
    """
    A mixin for displaying a list of objects

    When used with router, it will map the following operations to actions on the viewset

    ====== ======================== =============== ======================
    Method Path                     Action          Route Name
    ====== ======================== =============== ======================
    GET    /                        list            <resource name>-list
    ====== ======================== =============== ======================
    """

    def list(self, request, *args, **kwargs):
        """List action for displaying a list of objects"""
        self.object_list = self.get_queryset()
        context = self.get_list_context_data()
        return self.render_to_response(context)

    def get_list_context_object_name(self, object_list):
        """Get the name to use for the object."""
        model = self.get_model()
        return "%s_list" % model.__name__.lower()

    def get_list_context_data(self, **kwargs):
        """Returns context data for list action"""
        queryset = self.object_list
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_list_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {"paginator": paginator, "page_obj": page, "is_paginated": is_paginated, "object_list": queryset}
        else:
            context = {"paginator": None, "page_obj": None, "is_paginated": False, "object_list": queryset}
        if context_object_name is not None:
            context[context_object_name] = queryset
        context.update(kwargs)
        return super(ListModelMixin, self).get_context_data(**context)


class RetrieveModelMixin(BaseSingleObjectMixin):
    """
    A mixin for displaying a single object

    When used with router, it will map the following operations to actions on the viewset

    ====== ======================== =============== ======================
    Method Path                     Action          Route Name
    ====== ======================== =============== ======================
    GET    /<pk>/                   retrieve        <resource name>-detail
    ====== ======================== =============== ======================
    """

    def retrieve(self, request, *args, **kwargs):
        """List action for displaying a single object"""
        self.object = self.get_object()
        context = self.get_detail_context_data(object=self.object)
        return self.render_to_response(context)

    def get_url_kwargs(self, obj):
        info = meta.model_info(type(obj))
        return {key: getattr(obj, key) for key in info.primary_keys}

    def get_detail_context_object_name(self, obj):
        """Get the name to use for the object."""
        model = self.get_model()
        return model.__name__.lower()

    def get_detail_context_data(self, **kwargs):
        """
        Returns detail context data for template rendering
        """
        context = {}
        if self.object:
            context["object"] = self.object
            context_object_name = self.get_detail_context_object_name(self.object)
            if context_object_name:
                context[context_object_name] = self.object
        context.update(kwargs)
        return super(RetrieveModelMixin, self).get_context_data(**context)


class ModelFormMixin(FormMixin, RetrieveModelMixin):
    """
    Common mixin for handling sqlalchemy model forms in viewsets
    """

    fields = None
    form_class = None
    success_url = None

    def get_form_class(self):
        """
        Returns the form class to be used
        """
        if self.fields is not None and self.form_class:
            raise ImproperlyConfigured("Specifying both 'fields' and 'form_class' is not permitted.")

        if self.form_class:
            return self.form_class

        model = self.get_model()
        return modelform_factory(model, fields=self.fields, session=self.get_session())

    def get_form_kwargs(self):
        """
        Returns the keyword arguments for instantiating the form
        """
        kwargs = super(ModelFormMixin, self).get_form_kwargs()
        if hasattr(self, "object"):
            kwargs.update({"instance": self.object})

        kwargs["session"] = self.get_session()
        return kwargs

    def get_success_url(self):
        """
        Return the URL to redirect to after processing a valid form.
        """
        if self.success_url:
            return self.success_url.format(**vars(self.object))

        raise ImproperlyConfigured(
            "No URL to redirect to. Either provide a url or override this function to return a url"
        )

    def process_form(self, form):
        """
        Checks if form is valid and processes accordingly
        """
        if form.is_valid():
            return self.form_valid(form)

        return self.form_invalid(form)

    def form_valid(self, form):
        """
        Processes a valid form
        """
        self.object = form.save()
        return super(ModelFormMixin, self).form_valid(form)

    def form_invalid(self, form):
        """
        Handles invalid form
        """
        return self.render_to_response(self.get_form_context_data(form=form))

    def get_form_context_data(self, **kwargs):
        """
        Return form context data for template rendering
        """
        if "form" not in kwargs:
            kwargs["form"] = self.get_form()
        return self.get_detail_context_data(**kwargs)


class CreateModelMixin(ModelFormMixin):
    """
    A mixin for supporting creating objects

    When used with router, it will map the following operations to actions on the viewset

    ====== ======================== =============== ======================
    Method Path                     Action          Route Name
    ====== ======================== =============== ======================
    POST   /                        create          <resource name>-list
    GET    /new/                    new             <resource name>-new
    ====== ======================== =============== ======================
    """

    def new(self, request, *args, **kwargs):
        """New action for displaying a form for creating an object"""
        return self.render_to_response(self.get_create_context_data())

    def create(self, request, *args, **kwargs):
        """Create action for creating an object"""
        form = self.get_form()
        return self.process_form(form)

    def get_create_context_data(self, **kwargs):
        """
        Returns new context data for template rendering
        """
        return self.get_form_context_data(**kwargs)


class UpdateModelMixin(ModelFormMixin):
    """
    A mixin for supporting updating objects

    When used with router, it will map the following operations to actions on the viewset

    ====== ======================== =============== ======================
    Method Path                     Action          Route Name
    ====== ======================== =============== ======================
    GET    /<pk>/edit/              edit            <resource name>-edit
    POST   /<pk>/                   update          <resource name>-detail
    PUT    /<pk>/                   update          <resource name>-detail
    PATCH  /<pk>/                   update          <resource name>-detail
    ====== ======================== =============== ======================
    """

    def edit(self, request, *args, **kwargs):
        """Edit action for displaying a form for editing an object"""
        self.object = self.get_object()
        return self.render_to_response(self.get_update_context_data())

    def update(self, request, *args, **kwargs):
        """Update action for updating an object"""
        self.object = self.get_object()
        form = self.get_form()
        return self.process_form(form)

    def get_update_context_data(self, **kwargs):
        """
        Returns edit context data for template rendering
        """
        return self.get_form_context_data(**kwargs)


class DeleteModelMixin(RetrieveModelMixin):
    """
    A mixin for supporting deleting objects

    When used with router, it will map the following operations to actions on the viewset

    ====== ======================== =============== ======================
    Method Path                     Action          Route Name
    ====== ======================== =============== ======================
    GET    /<pk>/delete/            confirm_destoy  <resource name>-delete
    POST   /<pk>/delete/            destroy         <resource name>-delete
    DELETE /<pk>/                   destroy         <resource name>-detail
    ====== ======================== =============== ======================
    """

    destroy_success_url = None

    def confirm_destroy(self, request, *args, **kwargs):
        """Confirm_destory action for displaying deletion confirmation for an object"""
        self.object = self.get_object()
        return self.render_to_response(self.get_destroy_context_data())

    def destroy(self, request, *args, **kwargs):
        """Destroy action for deletion of an object"""
        self.object = self.get_object()
        self.perform_destoy(self.object)
        return HttpResponseRedirect(self.get_destroy_success_url())

    def perform_destoy(self, obj):
        """
        Performs the deletion operation
        """
        session = self.get_session()
        session.delete(obj)
        session.flush()

    def get_destroy_context_data(self, **kwargs):
        """
        Returns destory context data for rendering deletion confirmation page
        """
        return self.get_detail_context_data(**kwargs)

    def get_destroy_success_url(self):
        """
        Return the url to redirect to after successful deletion of an object
        """
        if self.destroy_success_url:
            return self.destroy_success_url.format(**vars(self.object))

        raise ImproperlyConfigured(
            "No URL to redirect to. Either provide a url or override this function to return a url"
        )
