import random

from django.utils.decorators import decorator_from_middleware_with_args
from django.utils.decorators import method_decorator
from rest_framework import status
from rest_framework.views import APIView

from authenticate.middleware import AuthorizatorMiddleware
from collaborators import defined_roles as dr
from collaborators.middleware import CollaborationAccessMiddleware, SubscriptionAccessMiddleware
from institute.models import Institute
from question.models import Question
from question_bank import serializers as question_bank_serializers
from question_bank.models import QUESTIONBANK_ACCESSIBILITY_PRIVATE, QUESTIONBANK_ACCESSIBILITY_PUBLIC, QuestionBank, \
    QuestionBankDefaultIcon
from question_bank.serializers import QuestionBankModelSerializer, QuestionBankGetSerializer, QuestionBankListInputSerializer
from utils.limitations import check_institute_limitations
from utils.myresponse import MyResponse
from utils.utils import sort_by_translator
from wikiazma.middleware import SerializerValidationMiddleware

serializer_decorator = decorator_from_middleware_with_args(SerializerValidationMiddleware)

contribution_decorator = decorator_from_middleware_with_args(CollaborationAccessMiddleware)
subscribtion_decorator = decorator_from_middleware_with_args(SubscriptionAccessMiddleware)
authorization_decorator = decorator_from_middleware_with_args(AuthorizatorMiddleware)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.CreateQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
class CreateQuestionBank(APIView):

    def post(self, request):
        context_institute = request.middleware_institute
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        name = request.middleware_serializer_data.get('name')
        description = request.middleware_serializer_data.get('description')
        public = request.middleware_serializer_data.get('public')
        icon = request.middleware_serializer_data.get('icon')
        default_icon_id = request.middleware_serializer_data.get('default_icon_id')
        # ########################## Block 2 ################################
        # check user limitations to create QuestionBank
        if not check_institute_limitations(QuestionBank, request.middleware_institute):
            return MyResponse(request, {'status': 'error', 'message': 'Payment Required'},
                              status=status.HTTP_402_PAYMENT_REQUIRED)
        # ########################## Block 3 ################################
        # checks if any question bank exists with incoming name and middleware_institute
        question_bank_icon = None
        if icon:
            question_bank_icon = icon
        elif default_icon_id:
            try:
                question_bank_default_icon = QuestionBankDefaultIcon.objects.get(id=default_icon_id)
                question_bank_icon = question_bank_default_icon.icon
            except:
                return MyResponse(request, {'status': 'error', 'message': 'question_bank_default_icon not found'},
                                  status=status.HTTP_400_BAD_REQUEST)
        else:

            question_bank_default_icons_query = QuestionBankDefaultIcon.objects.all()
            if question_bank_default_icons_query:
                question_bank_default_icons_count = len(question_bank_default_icons_query)
                question_bank_default_icon = random.randint(0, question_bank_default_icons_count - 1)
                question_bank_icon = question_bank_default_icons_query[question_bank_default_icon].icon

        accessibility = QUESTIONBANK_ACCESSIBILITY_PUBLIC if public else QUESTIONBANK_ACCESSIBILITY_PRIVATE

        question_bank = QuestionBank.objects.create(name=name, institute=context_institute, description=description,
                                                    accessibility=accessibility, icon=question_bank_icon)

        # update context_institute with signals

        question_bank_data = QuestionBankModelSerializer(question_bank,
                                                         context={"context_institute": request.middleware_institute}).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'question_bank': question_bank_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.EditQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], QuestionBank, 'id', 'question_bank_id'),
                  name='post')
class EditQuestionBank(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        name = request.middleware_serializer_data.get('name')
        description = request.middleware_serializer_data.get('description')
        public = request.middleware_serializer_data.get('public')
        icon = request.middleware_serializer_data.get('icon')
        default_icon_id = request.middleware_serializer_data.get('default_icon_id')

        question_bank = request.middleware_model_record

        # ########################## Block 2 ################################
        # update question_bank data
        if icon:
            question_bank.icon = icon
        elif default_icon_id:
            try:
                question_bank_default_icon = QuestionBankDefaultIcon.objects.get(id=default_icon_id)
                question_bank.icon = question_bank_default_icon.icon
            except:
                return MyResponse(request, {'status': 'error', 'message': 'question_bank_default_icon not found'},
                                  status=status.HTTP_400_BAD_REQUEST)
        if name is not None:
            question_bank.name = name

        if description is not None:
            question_bank.description = description

        if public == True:
            question_bank.accessibility = QUESTIONBANK_ACCESSIBILITY_PUBLIC

        question_bank.save()

        question_bank_data = QuestionBankModelSerializer(question_bank,
                                                         context={"context_institute": request.middleware_institute}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'question_bank': question_bank_data}},
                          status=status.HTTP_200_OK)


def handle_filters_template_array(filters_template_array, questions_list):
    filters = []
    total_stock = 0
    for order, filter_template in enumerate(filters_template_array):
        filter, stock = handle_filters_template(filter_template, questions_list, order)
        filters.append(filter)
        total_stock += stock

    if len(filters_template_array) == 0:
        total_stock = len(questions_list)

    return filters, total_stock


def handle_filters_template(filters_template, questions_list, order):
    raw_values = find_question_key_values_list(questions_list, filters_template['key'])

    values = []
    total_stock = 0
    for value_order, (value, questions) in enumerate(raw_values.items()):
        children, stock = handle_filters_template_array(filters_template['children'], questions)
        total_stock += stock
        values.append({
            'value': value,
            'stock': stock,
            'order': value_order,
            'children': children
        })
    filter = {'key': filters_template['key'], 'order': order, 'stock': total_stock, 'values': values}
    return filter, total_stock


def find_question_key_values_list(questions_list, searching_key):
    values = {}
    for question in questions_list:
        for key, value in question.tags.items():
            if key == searching_key:
                if value not in values:
                    values[value] = []
                values[value].append(question)

    return values


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.EditQuestionBankFiltersInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], QuestionBank, 'id', 'question_bank_id'),
                  name='post')
class EditQuestionBankFilters(APIView):

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

        question_bank = request.middleware_model_record

        # ########################## Block 2 ################################
        # update question_bank data

        questions_list = Question.objects.filter(question_bank=question_bank)
        filters, stock = handle_filters_template_array(filters_template, questions_list)

        question_bank.filters_template = filters_template
        question_bank._filters = filters

        question_bank.save()

        question_bank_data = QuestionBankGetSerializer(question_bank,
                                                       context={"context_institute": request.middleware_institute}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'question_bank': question_bank_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.ArchiveQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], QuestionBank, 'id', 'question_bank_id'),
                  name='post')
class ArchiveQuestionBank(APIView):
    def post(self, request):
        question_bank = request.middleware_model_record
        question_bank.archived = True
        question_bank.save()
        return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.UnsubscribeQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
@method_decorator(subscribtion_decorator(QuestionBank, 'id', 'question_bank_id'), name='post')
class UnsubscribeQuestionBank(APIView):
    def post(self, request):
        question_bank_id = request.middleware_serializer_data.get('question_bank_id')
        context_institute = request.middleware_institute
        context_institute.unsubscribe_qb(question_bank_id)

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


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.GetQuestionBankInputSerializer), name='post')
# If a user does not have __PERMISSION_READ_QUESTIONBANK__ on the context_institute_id,
# he cannot even see public free question_banks on behalf of the context_institute_id
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
@method_decorator(subscribtion_decorator(QuestionBank, 'id', 'question_bank_id'), name='post')
class GetQuestionBank(APIView):

    def post(self, request):
        question_bank = request.middleware_question_bank
        question_bank_data = QuestionBankGetSerializer(question_bank,
                                                       context={"context_institute": request.middleware_institute}, ).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'question_bank': question_bank_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(QuestionBankListInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
class QuestionBankList(APIView):

    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')
        q = request.middleware_serializer_data.get('q')
        context_institute = request.middleware_institute
        archived_filter = request.middleware_serializer_data.get('archived_filter')
        sort_by = request.middleware_serializer_data.get('sort_by')
        sort_by_attr = sort_by_translator(sort_by)

        # ########################## Block 2 ################################
        # try to get appropriate question_banks from db and return question_banks_data

        qb_queryset = QuestionBank.objects.filter(pk__in=context_institute._subscribed_question_banks)

        # query name filter
        if q:
            qb_queryset = qb_queryset.filter(name__icontains=q)

        # archived  filter
        if archived_filter == 'exclude':
            qb_queryset = qb_queryset.filter(archived=False)
        elif archived_filter == 'only':
            qb_queryset = qb_queryset.filter(archived=True)
        total_count = qb_queryset.count()

        qb_queryset = qb_queryset.order_by(sort_by_attr)[skip:skip + take]

        question_banks_data = QuestionBankModelSerializer(qb_queryset,
                                                          context={"context_institute": request.middleware_institute},
                                                          many=True).data

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


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.QuestionBankMarketPlaceInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
class QuestionBankMarketPlace(APIView):
    def post(self, request):
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        q = request.middleware_serializer_data.get('q')
        free_filter = request.middleware_serializer_data.get('free_filter')
        featured_filter = request.middleware_serializer_data.get('featured_filter')

        context_institute = request.middleware_institute

        question_banks = QuestionBank.objects.filter(archived=False).exclude(pk__in=context_institute._subscribed_question_banks)

        if featured_filter == 'exclude':
            question_banks = question_banks.filter(featured=False)
        elif featured_filter == "only":
            question_banks = question_banks.filter(featured=True)

        if free_filter == 'exclude':
            question_banks = question_banks.filter(price__gt=0)
        elif free_filter == "only":
            question_banks = question_banks.filter(price=0)

        if q:
            question_banks = question_banks.filter(name__icontains=q)
        total_count = question_banks.count()
        question_banks = question_banks.order_by("-created_at")[skip:skip + take]

        questions_bank_data = QuestionBankModelSerializer(question_banks, context={"context_institute": context_institute},
                                                          many=True).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'question_banks': questions_bank_data, 'total_count': total_count}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.QuestionBankPickInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_WRITE_QUESTIONBANK__], Institute, 'id', 'context_institute_id'),
                  name='post')
class QuestionBankPick(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        question_bank_id = request.middleware_serializer_data.get('question_bank_id')
        context_institute = request.middleware_institute

        try:
            question_bank = QuestionBank.objects.get(id=question_bank_id)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'BadRequest'}, status=status.HTTP_400_BAD_REQUEST)

        if question_bank.public and question_bank.price == 0:
            context_institute.subscribe_qb(question_bank.pk)
            return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)

        elif question_bank.public and question_bank.price > 0:
            ##TODO: pay
            return MyResponse(request, {'status': 'PAYMENT REQUIRED', 'message': 'PAYMENT REQUIRED'},
                              status=status.HTTP_402_PAYMENT_REQUIRED)

        else:
            return MyResponse(request, {'status': 'error', 'message': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
class QuestionBankDefaultIconsList(APIView):
    def post(self, request):
        question_bank_default_icons_query = QuestionBankDefaultIcon.objects.all()

        question_bank_default_icons_data = question_bank_serializers.QuestionBankDefaultIconsListModelSerializer(
            question_bank_default_icons_query, many=True).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'icons': question_bank_default_icons_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.TagsListOfAQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__, dr.__PERMISSION_READ_TAG__], Institute, 'id',
                                         'context_institute_id'), name='post')
@method_decorator(subscribtion_decorator(QuestionBank, 'id', 'question_bank_id'), name='post')
class TagsListOfAQuestionBank(APIView):

    def post(self, request):
        ########################### Block 1 ################################
        question_bank = request.middleware_question_bank
        tags = question_bank._tags

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


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.TagsKeysListOfAQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__, dr.__PERMISSION_READ_TAG__], Institute, 'id',
                                         'context_institute_id'), name='post')
@method_decorator(subscribtion_decorator(QuestionBank, 'id', 'question_bank_id'), name='post')
class TagsKeysListOfAQuestionBank(APIView):

    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')
        q = request.middleware_serializer_data.get('q')
        # ########################## Block 2 ################################
        question_bank = request.middleware_question_bank
        tags = question_bank.tags

        tags_data = [tag.key for tag in tags if q in tag.key][skip:skip + take]
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'keys': tags_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorization_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(question_bank_serializers.TagsValuesListOfAQuestionBankInputSerializer), name='post')
@method_decorator(contribution_decorator([dr.__PERMISSION_READ_QUESTIONBANK__, dr.__PERMISSION_READ_TAG__], Institute, 'id',
                                         'context_institute_id'), name='post')
@method_decorator(subscribtion_decorator(QuestionBank, 'id', 'question_bank_id'), name='post')
class TagsValuesListOfAQuestionBank(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        key = request.middleware_serializer_data.get('key')
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        q = request.middleware_serializer_data.get('q')
        # ########################## Block 2 ################################
        question_bank = request.middleware_question_bank
        tags = question_bank.tags

        selected_tag = [tag for tag in tags if tag.key == key]

        if selected_tag:
            tags_data = [filter_value.value for filter_value in selected_tag[0].values if q in filter_value.value][
                        skip:skip + take]
        else:
            tags_data = []

        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'values': tags_data}},
                          status=status.HTTP_200_OK)
