"""
Generic views using attrs shape classes instead of DRF serializers.
"""
from mantle import Query
from mantle.db import default_converter
from rest_framework.generics import GenericAPIView as DRFGenericAPIView

from .mixins import (
    CreateModelMixin,
    DestroyModelMixin,
    ListModelMixin,
    RetrieveModelMixin,
    UpdateModelMixin,
)


class GenericAPIView(DRFGenericAPIView):
    """
    A DRF GenericAPIView that uses attrs shape classes instead of serializers.

    Set ``shape_class`` on the view to an attrs-decorated class.
    """

    shape_class = None

    def get_shape_class(self):
        """
        Return the shape class to use for structuring/unstructuring.

        Defaults to ``self.shape_class``.
        """
        assert self.shape_class is not None, (
            "'%s' should either include a `shape_class` attribute, "
            "or override the `get_shape_class()` method." % self.__class__.__name__
        )
        return self.shape_class

    def get_query(self, queryset=None):
        """
        Return a ``mantle.Query`` for the current queryset and shape class.
        """
        if queryset is None:
            queryset = self.get_queryset()
        return Query(queryset, self.get_shape_class())

    def structure(self, data):
        """Structure request data into a shape instance."""
        return default_converter.structure(data, self.get_shape_class())

    def unstructure(self, shape):
        """Unstructure a shape instance to a JSON-serialisable dict."""
        return default_converter.unstructure(shape)

    # Override get_serializer_class to return None — we don't use serializers.
    def get_serializer_class(self):
        return None


# Concrete view classes


class CreateAPIView(CreateModelMixin, GenericAPIView):
    """Concrete view for creating a model instance."""

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


class ListAPIView(ListModelMixin, GenericAPIView):
    """Concrete view for listing a queryset."""

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


class RetrieveAPIView(RetrieveModelMixin, GenericAPIView):
    """Concrete view for retrieving a model instance."""

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


class DestroyAPIView(DestroyModelMixin, GenericAPIView):
    """Concrete view for deleting a model instance."""

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)


class UpdateAPIView(UpdateModelMixin, GenericAPIView):
    """Concrete view for updating a model instance."""

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)


class ListCreateAPIView(ListModelMixin, CreateModelMixin, GenericAPIView):
    """Concrete view for listing and creating model instances."""

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

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


class RetrieveUpdateAPIView(RetrieveModelMixin, UpdateModelMixin, GenericAPIView):
    """Concrete view for retrieving and updating a model instance."""

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

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)


class RetrieveDestroyAPIView(RetrieveModelMixin, DestroyModelMixin, GenericAPIView):
    """Concrete view for retrieving and deleting a model instance."""

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

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)


class RetrieveUpdateDestroyAPIView(
    RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView
):
    """Concrete view for retrieving, updating, and deleting a model instance."""

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

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
