from django.db import transaction

from rest_framework import decorators
from rest_framework import exceptions
from rest_framework import response
from rest_framework import status

from . import filters
from . import models
from . import mutations as common_mutations
from . import serializers
from . import signals
from . import tasks
from . import utils


def action_route(serializer_class):
    @decorators.action(methods=["post"], detail=False)
    def action(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = serializer_class(request.data, queryset=queryset)
        serializer.is_valid(raise_exception=True)
        result = serializer.save()
        return response.Response(result, status=200)

    return action


def mutations_route(types):
    """
    Given a queryset and a list of mutation types, return a view
    that can be included in any viewset, and serve:

    GET /{id}/mutations/ - list of mutations for the given object
    POST /{id}/mutations/ - create a mutation for the given object
    """

    @transaction.atomic
    def mutations(self, request, *args, **kwargs):
        obj = self.get_object()
        if request.method == "GET":
            queryset = models.Mutation.objects.get_for_target(obj).filter(
                type__in=types
            )
            queryset = queryset.order_by("-creation_date")
            filterset = filters.MutationFilter(request.GET, queryset=queryset)
            page = self.paginate_queryset(filterset.qs)
            if page is not None:
                serializer = serializers.APIMutationSerializer(page, many=True)
                return self.get_paginated_response(serializer.data)

            serializer = serializers.APIMutationSerializer(queryset, many=True)
            return response.Response(serializer.data)
        if request.method == "POST":
            if not request.user.is_authenticated:
                raise exceptions.NotAuthenticated()
            serializer = serializers.APIMutationSerializer(
                data=request.data, context={"registry": common_mutations.registry}
            )
            serializer.is_valid(raise_exception=True)
            if not common_mutations.registry.has_perm(
                actor=request.user.actor,
                type=serializer.validated_data["type"],
                obj=obj,
                perm="approve"
                if serializer.validated_data.get("is_approved", False)
                else "suggest",
            ):
                raise exceptions.PermissionDenied()

            final_payload = common_mutations.registry.get_validated_payload(
                type=serializer.validated_data["type"],
                payload=serializer.validated_data["payload"],
                obj=obj,
            )
            mutation = serializer.save(
                created_by=request.user.actor,
                target=obj,
                payload=final_payload,
                is_approved=serializer.validated_data.get("is_approved", None),
            )
            if mutation.is_approved:
                utils.on_commit(tasks.apply_mutation.delay, mutation_id=mutation.pk)

            utils.on_commit(
                signals.mutation_created.send, sender=None, mutation=mutation
            )
            return response.Response(serializer.data, status=status.HTTP_201_CREATED)

    return decorators.action(methods=["get", "post"], detail=True)(mutations)