from datetime import timedelta

from django.db.models import Q
from django.utils import timezone
from rest_framework import status
from rest_framework.views import APIView
from django.utils.decorators import decorator_from_middleware_with_args
from django.utils.decorators import method_decorator

from authenticate.attemp_recaptcha import google_recaptcha_helper, GoogleRecaptchaException
from authenticate.middleware import AuthorizatorMiddleware
from collaborators.serializers import AccessSerializerForAllCollabrators, InvitationSerializer, AccessibleInstituteSerializer
from utils.pushers import invite_collaborator_email_content_builder, send_html_email, send_sms_invite
from wikiazma.middleware import SerializerValidationMiddleware
from utils.myresponse import MyResponse
from utils.limitations import check_institute_limitations
from authenticate.models import User
from collaborators import defined_roles as dr, serializers as collaborators_serializers, utils as collaborators_utils
from institute.models import Institute
from collaborators.models import Access, InviteAccess
from collaborators.middleware import CollaborationAccessMiddleware

serializer_validation_middleware = decorator_from_middleware_with_args(
    SerializerValidationMiddleware)
user_token_or_server_token_or_server_API_key_validation_and_authorization = decorator_from_middleware_with_args(
    AuthorizatorMiddleware)
check_access_permission_middleware = decorator_from_middleware_with_args(
    CollaborationAccessMiddleware)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.InviteInputSerializer), name='post')
class Invite(APIView):
    """
    This API class is responsible for handling incoming requests for inviting another user to a institute.
    We check access in the views level and not in middleware level,
    because it was based on input parameters of views.
    """

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        institute_id = request.middleware_serializer_data.get('institute_id')
        email = request.middleware_serializer_data.get('email')
        if email:
            email = email.lower()
        phone = request.middleware_serializer_data.get('phone')
        if phone:
            intab = '۱۲۳۴۵۶۷۸۹۰١٢٣٤٥٦٧٨٩٠'
            outtab = '12345678901234567890'
            translation_table = str.maketrans(intab, outtab)
            phone = phone.translate(translation_table)
        target_user_roles = request.middleware_serializer_data.get('roles')
        g_recaptcha_response = request.POST.get('g-recaptcha-response')
        caller = request.middleware_user
        # ########################## Block 2 ################################
        try:
            institute = Institute.objects.get(id=institute_id, sandbox=False)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'},
                              status=status.HTTP_403_FORBIDDEN)

        # Block 3 ################################: institute limitation restrict and permission restrict
        try:
            siu_access = Access.objects.get(
                user=request.middleware_user, institute=institute)
            siu_roles = siu_access.roles
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        other_invitation_count = InviteAccess.objects.filter(
            institute=institute, rejected=False, canceled=False).count()
        other_access_count = Access.objects.filter(institute=institute).count()

        if other_invitation_count + other_access_count >= institute.best_max_collaborators_per_institute:
            return MyResponse(request, {'status': 'error', 'message': f'The capacity to send invitations to each institution is {institute.best_max_collaborators_per_institute} people'},
                              status=status.HTTP_403_FORBIDDEN)

        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(siu_roles, target_user_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        # ########################## Block 4 ################################
        failed_invitation_attempts = InviteAccess.objects.filter(Q(rejected=True) | Q(canceled=True), modifire=request.middleware_user,
                                                                 created_at__gt=timezone.now() - timedelta(hours=72)).count()

        if failed_invitation_attempts >= 10:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        # ########################## Block 5 ################################
        attempts = InviteAccess.objects.filter(
            modifire=request.middleware_user, created_at__gt=timezone.now() - timedelta(hours=24)).count()
        if attempts >= 5:
            try:
                google_recaptcha_helper(g_recaptcha_response)
            except GoogleRecaptchaException as e:
                return e.my_response
        # ########################## Block 6 ################################
        invited_user = None
        if email:
            try:
                invited_user = User.objects.get(email=email)
            except:
                pass
        elif phone:
            try:
                invited_user = User.objects.get(phone=phone)
            except:
                pass

        # Block 7 ################################: restrict

        other_active_invitation_for_target_user_on_this_institute = InviteAccess.objects.filter(institute=institute, invited_email=email,
                                                                                                invited_phone=phone, rejected=False,
                                                                                                canceled=False)
        if len(other_active_invitation_for_target_user_on_this_institute) != 0:
            return MyResponse(request, {'status': 'error', 'message': 'This user is already invited'}, status=status.HTTP_403_FORBIDDEN)

        if invited_user:
            other_access_for_target_user_on_this_institute = Access.objects.filter(
                institute=institute, user=invited_user)
            if len(other_access_for_target_user_on_this_institute) != 0:
                return MyResponse(request, {'status': 'error', 'message': 'This user is already a collaborator'},
                                  status=status.HTTP_403_FORBIDDEN)

        # Block 8 ################################: save

        invitation = InviteAccess.objects.create(institute=institute,
                                                 invited_phone=phone, invited_user=invited_user,
                                                 modifire=request.middleware_user, invited_email=email,
                                                 roles=target_user_roles,
                                                 )

        # Block 9 ################################: send message :TODO
        full_name = "-"
        if caller.first_name and caller.last_name:
            full_name = f"{caller.first_name} {caller.last_name}"
        elif caller.first_name:
            full_name = caller.first_name
        elif caller.last_name:
            full_name = caller.last_name

        if email:
            content = invite_collaborator_email_content_builder(
                request, institute_name=institute.name, caller_full_name=full_name)

            send_html_email(content, email)
        elif phone:
            send_sms_invite(to=phone, caller_name=full_name,
                            institute_name=institute.name)

        invite_access_record_data = collaborators_serializers.InvitationSerializer(
            invitation).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'access': invite_access_record_data},
                          status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.ListInvitationInputSerializer), name='post')
@method_decorator(check_access_permission_middleware([dr.__PERMISSION_READ_COLLABORATORS__], Institute, 'id', 'institute_id'), name='post')
class ListInvitations(APIView):
    def post(self, request):
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        institute = request.middleware_model_record

        queryset = InviteAccess.objects.filter(institute=institute, canceled=False, rejected=False)
        total_count = queryset.count()
        invitations_query = queryset.order_by(
            'created_at')[skip:skip + take]

        invitations_data = InvitationSerializer(
            invitations_query, many=True).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'invitations': invitations_data, 'total_count':total_count}},
                          status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.ListMyInvitationInputSerializer), name='post')
class ListMyInvitations(APIView):
    def post(self, request):
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        caller = request.middleware_user

        queryset = InviteAccess.objects.filter(
            invited_user=caller, canceled=False, rejected=False)

        total_count = queryset.count()

        invitations_query = queryset.order_by('created_at')[skip:skip + take]

        invitations_data = InvitationSerializer(
            invitations_query, many=True).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'invitations': invitations_data,'total_count':total_count}},
                          status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.AcceptInvitationInputSerializer), name='post')
class AcceptInvitation(APIView):
    """
    This API class is responsible for handling incoming requests for accepting an invitaion to a institute.
    """

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        invitation_id = request.middleware_serializer_data.get('invitation_id')

        # ########################## Block 2 ################################
        # checks if any invite access record exists with authenticated user and incoming key
        try:
            invitation = InviteAccess.objects.get(
                invited_user=request.middleware_user, id=invitation_id, canceled=False, rejected=False)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'We cant find your invitation for this institute!'},
                              status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 3 ################################

        access_query = Access.objects.filter(
            user=request.middleware_user, institute=invitation.institute)
        if len(access_query) != 0:
            return MyResponse(request, {'status': 'error', 'message': 'You are a member of this institution!'},
                              status=status.HTTP_403_FORBIDDEN)

        # ########################## Block 4 ################################
        # create an access record with invite access record attributes and then deletes the invite access record
        access = Access.objects.create(
            user=invitation.invited_user,
            institute=invitation.institute,
            roles=invitation.roles,
            institute_name=invitation.institute.name,
            institute_created_at=invitation.institute.created_at,
        )

        invitation.delete()

        # ########################## Block 5################################
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.RejectInvitationInputSerializer), name='post')
class RejectInvitation(APIView):
    """
    This API class is responsible for handling incoming requests for accepting an invitaion to a institute.
    """

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        invitation_id = request.middleware_serializer_data.get('invitation_id')

        # ########################## Block 2 ################################
        # checks if any invite access record exists with authenticated user and incoming key
        try:
            invitation = InviteAccess.objects.get(
                invited_user=request.middleware_user, id=invitation_id, rejected=False, canceled=False)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'We cant find your invitation for this institute!'},
                              status=status.HTTP_403_FORBIDDEN)

        # ########################## Block 3 ################################

        invitation.rejected = True
        invitation.save()

        # ########################## Block 4################################
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.RemoveInvitationInputSerializer), name='post')
class RemoveInvitation(APIView):
    def post(self, request):
        # ########################## Block 1 ################################
        invitation_id = request.middleware_serializer_data.get('invitation_id')
        caller = request.middleware_user
        # ########################## Block 2 ################################
        try:
            invitation = InviteAccess.objects.get(
                id=invitation_id, canceled=False, rejected=False)
            target_user_roles = invitation.roles
        except:
            return MyResponse(request, {'status': 'not_found', 'message': 'invitation not found'}, status=status.HTTP_404_NOT_FOUND)
        # ########################## Block 3 ################################
        try:
            caller_access = Access.objects.get(
                user=caller, institute=invitation.institute)
            caller_roles = caller_access.roles
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, target_user_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 4 ################################

        invitation.canceled = True
        invitation.modifire = caller
        invitation.save()
        # ########################## Block 5################################
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.RemoveAccessInputSerializer), name='post')
class RemoveAccess(APIView):
    """
    This API class is responsible for handling incoming requests for removing access of a user to a institute.
    """

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        access_id = request.middleware_serializer_data.get('access_id')
        # ########################## Block 2 ################################
        # checks if any access record exists with incoming access_id or not
        # if exists, extracts its roles and assign it to access_roles variable
        try:
            access = Access.objects.get(id=access_id)
            target_user_roles = access.roles
        except:
            return MyResponse(request, {'status': 'not_found', 'message': 'Access not found'}, status=status.HTTP_404_NOT_FOUND)
        # ########################## Block 3 ################################
        try:
            caller_access = Access.objects.get(
                user=request.middleware_user, institute=access.institute)
            caller_roles = caller_access.roles
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 4 ################################
        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, target_user_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        access.delete()
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.AllCollaboratorsInputSerializer), name='post')
@method_decorator(check_access_permission_middleware([dr.__PERMISSION_READ_COLLABORATORS__], Institute, 'id', 'institute_id'), name='post')
class AllCollaborators(APIView):
    """
    This API class is responsible for handling incoming requests for getting all collaborators of a institute.
    """

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        search_words = request.middleware_serializer_data.get('search_words')
        # ########################## Block 2 ################################
        # gets the access_list & invite_access_list based on two situations: search_words comes or not!
        access_list = Access.objects.filter(
            institute=request.middleware_model_record)

        if search_words:
            access_list = access_list.filter(Q(user__email__icontains=search_words) | Q(user__phone__icontains=search_words) | Q(
                user__first_name__icontains=search_words) | Q(user__last_name__icontains=search_words))
        # ########################## Block 3 ################################
        # merge two above lists into one list and then apply skip & take to it
        # then serializes it to json and passed it to MyResponse
        total_count = access_list.count()
        
        skipped_take_list = access_list[skip:skip + take]
        access_list_data = AccessSerializerForAllCollabrators(
            skipped_take_list, many=True).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'collaborators': access_list_data, 'total_count':total_count}}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.LeaveCollaboratorInputSerializer), name='post')
class LeaveCollaborator(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        institute_id = request.middleware_serializer_data.get('institute_id')
        # ########################## Block 2 ################################
        # checking if authenticated user access to this institute or not!
        try:
            access = Access.objects.get(
                user=request.middleware_user, institute__id=institute_id)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 3 ################################
        # checks if authenticated user is not the owner of institute and then delete this access
        if dr.__ROLE_OWNER__ in access.roles:
            return MyResponse(request, {'status': 'error',
                                        'message': 'You can not leave your institute, you can delete this institute or transfer ownership to another user.'},
                              status=status.HTTP_401_UNAUTHORIZED)
        access.delete()
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.EditInvitationInputSerializer), name='post')
class EditInvitation(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        invitation_id = request.middleware_serializer_data.get('invitation_id')
        new_roles = request.middleware_serializer_data.get('new_roles')
        # ########################## Block 2 ################################
        try:
            invitation = InviteAccess.objects.get(
                id=invitation_id, canceled=False, rejected=False)
            target_user_roles = invitation.roles
        except:
            return MyResponse(request, {'status': 'not_found', 'message': 'invitation not found'}, status=status.HTTP_404_NOT_FOUND)

        try:
            caller_access = Access.objects.get(
                user=request.middleware_user, institute=invitation.institute)
            caller_roles = caller_access.roles
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 4 ################################
        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, target_user_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, new_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        invitation.roles = new_roles
        invitation.modifire = request.middleware_user
        invitation.save()
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(collaborators_serializers.EditCollaboratorInputSerializer), name='post')
class EditCollaborator(APIView):
    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        access_id = request.middleware_serializer_data.get('access_id')
        new_roles = request.middleware_serializer_data.get('new_roles')
        # ########################## Block 2 ################################
        # checks if any access record exists with incoming access_id or not
        # if exists, extracts its roles and assign it to access_roles variable
        try:
            access_record = Access.objects.get(id=access_id)
            target_user_roles = access_record.roles
        except:
            return MyResponse(request, {'status': 'not_found', 'message': 'Access not found'}, status=status.HTTP_404_NOT_FOUND)
        # ########################## Block 3 ################################
        try:
            caller_access = Access.objects.get(
                user=request.middleware_user, institute=access_record.institute)
            caller_roles = caller_access.roles
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
        # ########################## Block 4 ################################
        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, target_user_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        if not collaborators_utils.check_modify_collaborators_base_on_roles_cat(caller_roles, new_roles):
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)

        access_record.roles = new_roles
        access_record.save()
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)
