from django import forms
from django.core import paginator
from django.db import transaction
from django.http import HttpResponse, Http404
from django.urls import reverse
from rest_framework import mixins, response, viewsets
from rest_framework.decorators import detail_route, list_route

from funkwhale_api.common import preferences
from funkwhale_api.music import models as music_models
from funkwhale_api.users.permissions import HasUserPermission

from . import (
    actors,
    authentication,
    filters,
    library,
    models,
    permissions,
    renderers,
    serializers,
    tasks,
    utils,
    webfinger,
)


class FederationMixin(object):
    def dispatch(self, request, *args, **kwargs):
        if not preferences.get("federation__enabled"):
            return HttpResponse(status=405)
        return super().dispatch(request, *args, **kwargs)


class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    lookup_field = "user__username"
    lookup_value_regex = ".*"
    authentication_classes = [authentication.SignatureAuthentication]
    permission_classes = []
    renderer_classes = [renderers.ActivityPubRenderer]
    queryset = models.Actor.objects.local().select_related("user")
    serializer_class = serializers.ActorSerializer

    @detail_route(methods=["get", "post"])
    def inbox(self, request, *args, **kwargs):
        return response.Response({}, status=200)

    @detail_route(methods=["get", "post"])
    def outbox(self, request, *args, **kwargs):
        return response.Response({}, status=200)


class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
    lookup_field = "actor"
    lookup_value_regex = "[a-z]*"
    authentication_classes = [authentication.SignatureAuthentication]
    permission_classes = []
    renderer_classes = [renderers.ActivityPubRenderer]

    def get_object(self):
        try:
            return actors.SYSTEM_ACTORS[self.kwargs["actor"]]
        except KeyError:
            raise Http404

    def retrieve(self, request, *args, **kwargs):
        system_actor = self.get_object()
        actor = system_actor.get_actor_instance()
        data = actor.system_conf.serialize()
        return response.Response(data, status=200)

    @detail_route(methods=["get", "post"])
    def inbox(self, request, *args, **kwargs):
        system_actor = self.get_object()
        handler = getattr(system_actor, "{}_inbox".format(request.method.lower()))

        try:
            handler(request.data, actor=request.actor)
        except NotImplementedError:
            return response.Response(status=405)
        return response.Response({}, status=200)

    @detail_route(methods=["get", "post"])
    def outbox(self, request, *args, **kwargs):
        system_actor = self.get_object()
        handler = getattr(system_actor, "{}_outbox".format(request.method.lower()))
        try:
            handler(request.data, actor=request.actor)
        except NotImplementedError:
            return response.Response(status=405)
        return response.Response({}, status=200)


class WellKnownViewSet(viewsets.GenericViewSet):
    authentication_classes = []
    permission_classes = []
    renderer_classes = [renderers.JSONRenderer, renderers.WebfingerRenderer]

    @list_route(methods=["get"])
    def nodeinfo(self, request, *args, **kwargs):
        if not preferences.get("instance__nodeinfo_enabled"):
            return HttpResponse(status=404)
        data = {
            "links": [
                {
                    "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
                    "href": utils.full_url(reverse("api:v1:instance:nodeinfo-2.0")),
                }
            ]
        }
        return response.Response(data)

    @list_route(methods=["get"])
    def webfinger(self, request, *args, **kwargs):
        if not preferences.get("federation__enabled"):
            return HttpResponse(status=405)
        try:
            resource_type, resource = webfinger.clean_resource(request.GET["resource"])
            cleaner = getattr(webfinger, "clean_{}".format(resource_type))
            result = cleaner(resource)
            handler = getattr(self, "handler_{}".format(resource_type))
            data = handler(result)
        except forms.ValidationError as e:
            return response.Response({"errors": {"resource": e.message}}, status=400)
        except KeyError:
            return response.Response(
                {"errors": {"resource": "This field is required"}}, status=400
            )

        return response.Response(data)

    def handler_acct(self, clean_result):
        username, hostname = clean_result

        if username in actors.SYSTEM_ACTORS:
            actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
        else:
            try:
                actor = models.Actor.objects.local().get(user__username=username)
            except models.Actor.DoesNotExist:
                raise forms.ValidationError("Invalid username")

        return serializers.ActorWebfingerSerializer(actor).data


class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
    authentication_classes = [authentication.SignatureAuthentication]
    permission_classes = [permissions.LibraryFollower]
    renderer_classes = [renderers.ActivityPubRenderer]

    def list(self, request, *args, **kwargs):
        page = request.GET.get("page")
        library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
        qs = (
            music_models.TrackFile.objects.order_by("-creation_date")
            .select_related("track__artist", "track__album__artist")
            .filter(library_track__isnull=True)
        )
        if page is None:
            conf = {
                "id": utils.full_url(reverse("federation:music:files-list")),
                "page_size": preferences.get("federation__collection_page_size"),
                "items": qs,
                "item_serializer": serializers.AudioSerializer,
                "actor": library,
            }
            serializer = serializers.PaginatedCollectionSerializer(conf)
            data = serializer.data
        else:
            try:
                page_number = int(page)
            except Exception:
                return response.Response({"page": ["Invalid page number"]}, status=400)
            p = paginator.Paginator(
                qs, preferences.get("federation__collection_page_size")
            )
            try:
                page = p.page(page_number)
                conf = {
                    "id": utils.full_url(reverse("federation:music:files-list")),
                    "page": page,
                    "item_serializer": serializers.AudioSerializer,
                    "actor": library,
                }
                serializer = serializers.CollectionPageSerializer(conf)
                data = serializer.data
            except paginator.EmptyPage:
                return response.Response(status=404)

        return response.Response(data)


class LibraryViewSet(
    mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet,
):
    permission_classes = (HasUserPermission,)
    required_permissions = ["federation"]
    queryset = models.Library.objects.all().select_related("actor", "follow")
    lookup_field = "uuid"
    filter_class = filters.LibraryFilter
    serializer_class = serializers.APILibrarySerializer
    ordering_fields = (
        "id",
        "creation_date",
        "fetched_date",
        "actor__domain",
        "tracks_count",
    )

    @list_route(methods=["get"])
    def fetch(self, request, *args, **kwargs):
        account = request.GET.get("account")
        if not account:
            return response.Response({"account": "This field is mandatory"}, status=400)

        data = library.scan_from_account_name(account)
        return response.Response(data)

    @detail_route(methods=["post"])
    def scan(self, request, *args, **kwargs):
        library = self.get_object()
        serializer = serializers.APILibraryScanSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        result = tasks.scan_library.delay(
            library_id=library.pk, until=serializer.validated_data.get("until")
        )
        return response.Response({"task": result.id})

    @list_route(methods=["get"])
    def following(self, request, *args, **kwargs):
        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
        queryset = (
            models.Follow.objects.filter(actor=library_actor)
            .select_related("actor", "target")
            .order_by("-creation_date")
        )
        filterset = filters.FollowFilter(request.GET, queryset=queryset)
        final_qs = filterset.qs
        serializer = serializers.APIFollowSerializer(final_qs, many=True)
        data = {"results": serializer.data, "count": len(final_qs)}
        return response.Response(data)

    @list_route(methods=["get", "patch"])
    def followers(self, request, *args, **kwargs):
        if request.method.lower() == "patch":
            serializer = serializers.APILibraryFollowUpdateSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            follow = serializer.save()
            return response.Response(serializers.APIFollowSerializer(follow).data)

        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
        queryset = (
            models.Follow.objects.filter(target=library_actor)
            .select_related("actor", "target")
            .order_by("-creation_date")
        )
        filterset = filters.FollowFilter(request.GET, queryset=queryset)
        final_qs = filterset.qs
        serializer = serializers.APIFollowSerializer(final_qs, many=True)
        data = {"results": serializer.data, "count": len(final_qs)}
        return response.Response(data)

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        serializer = serializers.APILibraryCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return response.Response(serializer.data, status=201)


class LibraryTrackViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    permission_classes = (HasUserPermission,)
    required_permissions = ["federation"]
    queryset = (
        models.LibraryTrack.objects.all()
        .select_related("library__actor", "library__follow", "local_track_file")
        .prefetch_related("import_jobs")
    )
    filter_class = filters.LibraryTrackFilter
    serializer_class = serializers.APILibraryTrackSerializer
    ordering_fields = (
        "id",
        "artist_name",
        "title",
        "album_title",
        "creation_date",
        "modification_date",
        "fetched_date",
        "published_date",
    )

    @list_route(methods=["post"])
    def action(self, request, *args, **kwargs):
        queryset = models.LibraryTrack.objects.filter(local_track_file__isnull=True)
        serializer = serializers.LibraryTrackActionSerializer(
            request.data, queryset=queryset, context={"submitted_by": request.user}
        )
        serializer.is_valid(raise_exception=True)
        result = serializer.save()
        return response.Response(result, status=200)