from django import forms
from django.conf import settings
from django.core import paginator
from django.db import transaction
from django.http import HttpResponse
from django.urls import reverse

from rest_framework import mixins
from rest_framework import permissions as rest_permissions
from rest_framework import response
from rest_framework import views
from rest_framework import viewsets
from rest_framework.decorators import list_route, detail_route
from rest_framework.serializers import ValidationError

from funkwhale_api.common import preferences
from funkwhale_api.common import utils as funkwhale_utils
from funkwhale_api.music.models import TrackFile
from funkwhale_api.users.permissions import HasUserPermission

from . import activity
from . import actors
from . import authentication
from . import filters
from . import library
from . import models
from . import permissions
from . import renderers
from . import serializers
from . import tasks
from . import utils
from . import 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 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:
            data = 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:
            data = 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)
        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)

        handler = getattr(self, 'handler_{}'.format(resource_type))
        data = handler(result)

        return response.Response(data)

    def handler_acct(self, clean_result):
        username, hostname = clean_result
        actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
        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 = 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:
                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)
        library = 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',
    )
    filter_class = filters.LibraryTrackFilter
    serializer_class = serializers.APILibraryTrackSerializer
    ordering_fields = (
        'id',
        'artist_name',
        'title',
        'album_title',
        'creation_date',
        'modification_date',
        'fetched_date',
        'published_date',
    )