import secrets

from django.urls import path
from rest_framework import status
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin

from authentication.models import Group, GroupInvite, GroupInviteIncoming, KnownIdentity, ToolshedUser
from authentication.signature_auth import SignatureAuthentication, SignatureAuthenticationLocal, \
    authenticate_request_against_local_users, split_grouphandle_or_throw, split_userhandle_or_throw, \
    verify_incoming_group_invite
from toolshed.serializers import GroupSerializer, GroupInviteIncomingSerializer


class Groups(APIView, ViewSetMixin):
    authentication_classes = [SignatureAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):  # /api/groups/
        groups = request.user.member_of_groups.all()
        return Response(GroupSerializer(groups, many=True).data)

    def post(self, request, format=None):  # /api/groups/
        name = request.data.get('name')
        if not name:
            return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'name is required'})
        if Group.objects.filter(name=name, domain=request.user.domain).exists():
            return Response(status=status.HTTP_409_CONFLICT, data={'status': 'a group with this name already exists'})
        group = Group.objects.create(name=name, domain=request.user.domain)
        group.members.add(request.user)
        return Response(status=status.HTTP_201_CREATED, data=GroupSerializer(group).data)


class GroupDetail(APIView, ViewSetMixin):
    authentication_classes = [SignatureAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, pk, format=None):  # /api/groups/<pk>/
        group = get_object_or_404(Group, pk=pk)
        if not group.is_member(request.user):
            return Response(status=status.HTTP_404_NOT_FOUND)
        return Response(GroupSerializer(group).data)


@api_view(['DELETE'])
@authentication_classes([SignatureAuthentication])
@permission_classes([IsAuthenticated])
def removeGroupMember(request, pk, identity_id, format=None):  # /api/groups/<pk>/members/<identity_id>/
    group = get_object_or_404(Group, pk=pk)
    if not group.is_member(request.user):
        return Response(status=status.HTTP_404_NOT_FOUND)
    member = get_object_or_404(group.members, pk=identity_id)
    if group.members.count() <= 1:
        return Response(status=status.HTTP_400_BAD_REQUEST,
                        data={'status': "cannot remove the group's last member"})
    group.members.remove(member)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(['POST'])
@authentication_classes([SignatureAuthentication])
@permission_classes([IsAuthenticated])
def createGroupInvite(request, pk, format=None):  # /api/groups/<pk>/invites/
    group = get_object_or_404(Group, pk=pk)
    if not group.is_member(request.user):
        return Response(status=status.HTTP_404_NOT_FOUND)
    invitee = request.data.get('invitee')
    if not invitee:
        return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'invitee is required'})
    try:
        invitee_username, invitee_domain = split_userhandle_or_throw(invitee)
    except ValueError:
        return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'invalid invitee handle'})
    if group.members.filter(username=invitee_username, domain=invitee_domain).exists():
        return Response(status=status.HTTP_208_ALREADY_REPORTED, data={'status': 'already a member'})
    secret = secrets.token_hex(64)
    GroupInvite.objects.create(group=group, invitee_username=invitee_username, invitee_domain=invitee_domain,
                                secret=secret)
    return Response(status=status.HTTP_201_CREATED, data={'secret': secret, 'status': 'pending'})


class GroupInvitesIncoming(APIView, ViewSetMixin):
    """/api/groupinvites/ - the invitee's own view of their pending invites, and the delivery
    endpoint an inviter's client posts to directly on the invitee's own home backend (see
    docs/design-in-progress/groups-mvp.md's invite/accept dance)."""
    def get(self, request, format=None):  # /api/groupinvites/ - only ever a local user checking their own invites
        raw_request = request.body.decode('utf-8')
        if not (user := authenticate_request_against_local_users(request, raw_request)):
            return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'})
        invites = user.group_invites_incoming.all()
        return Response(GroupInviteIncomingSerializer(invites, many=True).data)

    def post(self, request, format=None):  # /api/groupinvites/ - delivery, self-certified, caller isn't known here
        raw_request = request.body.decode('utf-8')
        for field in ('group', 'inviter', 'inviter_key', 'invitee', 'secret'):
            if field not in request.data:
                return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': f'missing {field}'})
        if not verify_incoming_group_invite(request, raw_request, 'inviter', 'inviter_key'):
            return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'})
        try:
            group_name, group_domain = split_grouphandle_or_throw(request.data['group'])
            inviter_username, inviter_domain = split_userhandle_or_throw(request.data['inviter'])
            invitee_username, invitee_domain = split_userhandle_or_throw(request.data['invitee'])
        except ValueError:
            return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'invalid handle'})
        try:
            invitee_user = ToolshedUser.objects.get(username=invitee_username, domain=invitee_domain)
        except ToolshedUser.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND, data={'status': 'invitee is not local to this server'})
        GroupInviteIncoming.objects.create(
            group_name=group_name, group_domain=group_domain,
            inviter_username=inviter_username, inviter_domain=inviter_domain,
            invitee_user=invitee_user, secret=request.data['secret'])
        return Response(status=status.HTTP_201_CREATED, data={'status': 'delivered'})


@api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated])
def declineGroupInvite(request, pk, format=None):  # /api/groupinvites/<pk>/
    get_object_or_404(request.user.group_invites_incoming, pk=pk).delete()
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(['POST'])
def acceptGroupInvite(request, format=None):  # /api/group_invites/accept/ - lands on the group's home backend
    raw_request = request.body.decode('utf-8')
    for field in ('group', 'invitee', 'invitee_key', 'secret'):
        if field not in request.data:
            return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': f'missing {field}'})
    if not verify_incoming_group_invite(request, raw_request, 'invitee', 'invitee_key'):
        return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'})
    try:
        group_name, group_domain = split_grouphandle_or_throw(request.data['group'])
        invitee_username, invitee_domain = split_userhandle_or_throw(request.data['invitee'])
    except ValueError:
        return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'invalid handle'})
    try:
        group = Group.objects.get(name=group_name, domain=group_domain)
    except Group.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND, data={'status': 'no such group here'})
    invite = GroupInvite.objects.filter(group=group, invitee_username=invitee_username,
                                        invitee_domain=invitee_domain, secret=request.data['secret'])
    if not invite.exists():
        return Response(status=status.HTTP_404_NOT_FOUND, data={'status': 'no matching invite'})
    identity, _ = KnownIdentity.objects.get_or_create(
        username=invitee_username, domain=invitee_domain, public_key=request.data['invitee_key'])
    group.members.add(identity)
    invite.delete()
    return Response(status=status.HTTP_201_CREATED, data={'status': 'accepted'})


urlpatterns = [
    path('groups/', Groups.as_view(), name='groups'),
    path('groups/<int:pk>/', GroupDetail.as_view(), name='group_detail'),
    path('groups/<int:pk>/members/<int:identity_id>/', removeGroupMember, name='remove_group_member'),
    path('groups/<int:pk>/invites/', createGroupInvite, name='create_group_invite'),
    path('groupinvites/', GroupInvitesIncoming.as_view(), name='group_invites_incoming'),
    path('groupinvites/<int:pk>/', declineGroupInvite, name='decline_group_invite'),
    path('group_invites/accept/', acceptGroupInvite, name='accept_group_invite'),
]
