from django.conf import settings
from django.db import transaction
from django.db.models import Q
from django.views.decorators.cache import never_cache

import django_filters
from rest_framework import generics, permissions, views, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, ParseError
from rest_framework.response import Response

from normandy.base.api.mixins import CachingViewsetMixin
from normandy.base.api.permissions import AdminEnabledOrReadOnly
from normandy.base.api.renderers import JavaScriptRenderer
from normandy.base.decorators import api_cache_control
from normandy.recipes.models import Action, ApprovalRequest, Client, Recipe, RecipeRevision
from normandy.recipes.api.filters import (
    BaselineCapabilitiesFilter,
    CharSplitFilter,
    EnabledStateFilter,
)
from normandy.recipes.api.v1.serializers import (
    ActionSerializer,
    ApprovalRequestSerializer,
    ClientSerializer,
    RecipeRevisionSerializer,
    RecipeSerializer,
    SignedActionSerializer,
    SignedRecipeSerializer,
)


class ActionViewSet(CachingViewsetMixin, viewsets.ReadOnlyModelViewSet):
    """Viewset for viewing recipe actions."""

    queryset = Action.objects.all()
    serializer_class = ActionSerializer
    pagination_class = None

    lookup_field = "name"
    lookup_value_regex = r"[_\-\w]+"

    @action(detail=False, methods=["GET"])
    @api_cache_control()
    def signed(self, request, pk=None):
        actions = self.filter_queryset(self.get_queryset()).exclude(signature=None)
        serializer = SignedActionSerializer(actions, many=True)
        return Response(serializer.data)


class ActionImplementationView(generics.RetrieveAPIView):
    """
    Retrieves the implementation code for an action. Raises a 404 if the
    given hash doesn't match the hash we've stored.
    """

    queryset = Action.objects.all()
    lookup_field = "name"

    permission_classes = []
    renderer_classes = [JavaScriptRenderer]
    pagination_class = None

    @api_cache_control(max_age=settings.IMMUTABLE_CACHE_TIME)
    def retrieve(self, request, name, impl_hash):
        action = self.get_object()
        if impl_hash != action.implementation_hash:
            raise NotFound("Hash does not match current stored action.")

        return Response(action.implementation)


class RecipeFilters(django_filters.FilterSet):
    enabled = EnabledStateFilter()
    action = django_filters.CharFilter(field_name="latest_revision__action__name")
    channels = CharSplitFilter("latest_revision__channels__slug")
    locales = CharSplitFilter("latest_revision__locales__code")
    countries = CharSplitFilter("latest_revision__countries__code")
    only_baseline_capabilities = BaselineCapabilitiesFilter(default_only_baseline=False)

    class Meta:
        model = Recipe
        fields = ["action", "enabled", "latest_revision__action"]


class SignedRecipeFilters(RecipeFilters):
    only_baseline_capabilities = BaselineCapabilitiesFilter(default_only_baseline=True)


class RecipeViewSet(CachingViewsetMixin, viewsets.ReadOnlyModelViewSet):
    """Viewset for viewing and uploading recipes."""

    queryset = (
        Recipe.objects.all()
        # Foreign keys
        .select_related("latest_revision")
        .select_related("latest_revision__action")
        .select_related("latest_revision__approval_request")
        # Many-to-many
        .prefetch_related("latest_revision__channels")
        .prefetch_related("latest_revision__countries")
        .prefetch_related("latest_revision__locales")
    )
    serializer_class = RecipeSerializer
    filterset_class = RecipeFilters
    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly, AdminEnabledOrReadOnly]
    pagination_class = None

    def get_queryset(self):
        queryset = self.queryset

        if self.request.GET.get("status") == "enabled":
            queryset = queryset.only_enabled()
        elif self.request.GET.get("status") == "disabled":
            queryset = queryset.only_disabled()

        if "text" in self.request.GET:
            text = self.request.GET.get("text")
            if "\x00" in text:
                raise ParseError("Null bytes in text")
            queryset = queryset.filter(
                Q(latest_revision__name__contains=text)
                | Q(latest_revision__extra_filter_expression__contains=text)
            )

        return queryset

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @transaction.atomic
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @action(detail=False, methods=["GET"], filterset_class=SignedRecipeFilters)
    @api_cache_control()
    def signed(self, request, pk=None):
        recipes = self.filter_queryset(self.get_queryset()).exclude(signature=None)
        serializer = SignedRecipeSerializer(recipes, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=["GET"])
    @api_cache_control()
    def history(self, request, pk=None):
        recipe = self.get_object()
        serializer = RecipeRevisionSerializer(
            recipe.revisions.all(), many=True, context={"request": request}
        )
        return Response(serializer.data)


class RecipeRevisionViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = (
        RecipeRevision.objects.all()
        .select_related("action")
        .select_related("approval_request")
        .select_related("recipe")
        # Many-to-many
        .prefetch_related("channels")
        .prefetch_related("countries")
        .prefetch_related("locales")
    )
    serializer_class = RecipeRevisionSerializer
    permission_classes = [AdminEnabledOrReadOnly, permissions.DjangoModelPermissionsOrAnonReadOnly]
    pagination_class = None


class ApprovalRequestViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = ApprovalRequest.objects.all()
    serializer_class = ApprovalRequestSerializer
    permission_classes = [AdminEnabledOrReadOnly, permissions.DjangoModelPermissionsOrAnonReadOnly]
    pagination_class = None


class ClassifyClient(views.APIView):
    authentication_classes = []
    permission_classes = []
    serializer_class = ClientSerializer

    @never_cache
    def get(self, request, format=None):
        client = Client(request)
        serializer = self.serializer_class(client, context={"request": request})
        return Response(serializer.data)