from django.contrib import admin
from django.shortcuts import get_object_or_404
from django.utils import timezone

from celery.result import allow_join_result
from celery.states import SUCCESS
from oauth2_provider.contrib.rest_framework import TokenHasScope
from rest_framework import generics, permissions, serializers
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.permissions import BasePermission
from rest_framework.response import Response
from rest_framework.views import APIView

from aleksis.apps.kort.models import Card, CardPrinter, CardPrintJob

admin.autodiscover()


class CorrectPrinterPermission(BasePermission):
    """Check whether the OAuth2 application belongs to the printer."""

    def has_object_permission(self, request, view, obj) -> bool:
        token = request.auth
        if token.application == obj.oauth2_application:
            return True
        return False


class CorrectJobPrinterPermission(BasePermission):
    """Check whether the OAuth2 application belongs to the job's printer."""

    def has_object_permission(self, request, view, obj) -> bool:
        token = request.auth
        if token.application == obj.printer.oauth2_application:
            return True
        return False


class CardPrinterSerializer(serializers.ModelSerializer):
    class Meta:
        model = CardPrinter
        fields = (
            "id",
            "name",
            "description",
            "location",
            "status",
            "status_label",
            "status_color",
            "status_icon",
            "status_text",
            "last_seen_at",
            "cups_printer",
            "generate_number_on_server",
            "card_detector",
        )


class CardPrinterStatusSerializer(serializers.ModelSerializer):
    class Meta:
        model = CardPrinter
        fields = ("status", "status_text")


class CardSerializer(serializers.ModelSerializer):
    class Meta:
        model = Card
        fields = ("id", "chip_number", "valid_until", "deactivated", "person", "pdf_file")


class CardPrintJobSerializer(serializers.ModelSerializer):
    card = CardSerializer()

    class Meta:
        model = CardPrintJob
        fields = ("id", "printer", "card", "status", "status_text")


class CardPrintJobStatusSerializer(serializers.ModelSerializer):
    class Meta:
        model = CardPrintJob
        fields = ("id", "status", "status_text")


class CardChipNumberSerializer(serializers.ModelSerializer):
    class Meta:
        model = Card
        fields = ("chip_number",)


class CardPrinterDetails(generics.RetrieveAPIView):
    """Show details about the card printer."""

    permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission]
    required_scopes = ["card_printer"]
    serializer_class = CardPrinterSerializer
    queryset = CardPrinter.objects.all()

    def get_object(self):
        token = self.request.auth
        return token.application.card_printers.all().first()


class CardPrinterUpdateStatus(generics.UpdateAPIView):
    """Update the status of the card printer."""

    permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission]
    required_scopes = ["card_printer"]
    serializer_class = CardPrinterStatusSerializer
    queryset = CardPrinter.objects.all()

    def update(self, request, *args, **kwargs):
        r = super().update(request, *args, **kwargs)
        instance = self.get_object()
        instance.last_seen_at = timezone.now()
        instance.save()
        return r


class GetNextPrintJob(APIView):
    """Get the next print job."""

    permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission]
    required_scopes = ["card_printer"]
    serializer_class = CardPrinterSerializer
    queryset = CardPrinter.objects.all()

    def get_object(self, pk):
        return get_object_or_404(CardPrinter, pk=pk)

    def get(self, request, pk, *args, **kwargs):
        printer = self.get_object(pk)
        job = printer.get_next_print_job()
        if not job:
            return Response({"status": "no_job"})
        serializer = CardPrintJobSerializer(job)
        return Response(serializer.data)


class CardPrintJobUpdateStatusView(generics.UpdateAPIView):
    """Update the status of the card printer."""

    permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectJobPrinterPermission]
    required_scopes = ["card_printer"]
    serializer_class = CardPrintJobStatusSerializer
    queryset = CardPrintJob.objects.all()


class CardPrintJobSetChipNumberView(generics.UpdateAPIView):
    """Update the status of the card printer."""

    permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectJobPrinterPermission]
    required_scopes = ["card_printer"]
    serializer_class = CardChipNumberSerializer
    queryset = CardPrintJob.objects.all()

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        card = instance.card

        if card.chip_number:
            raise ValidationError

        serializer = self.get_serializer(card, data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        result = instance.card.generate_pdf()

        with allow_join_result():
            result.wait()
            card.refresh_from_db()

        if result.status == SUCCESS and card.pdf_file:
            serializer = CardPrintJobSerializer(instance)
            instance.refresh_from_db()

            return Response(serializer.data)
        else:
            card.chip_number = None
            card.save()
            raise APIException("Error while generating PDF file")