from django.db.models import Q
from functools import reduce
from operator import and_, or_
from django.utils.decorators import method_decorator, decorator_from_middleware_with_args
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 import serializers as question_serializers
from question.models import Question, QUESTION_TYPE_MULTIPLE_CHOICE, get_query_from_tag_filters_list
from question_bank.models import QuestionBank, modify_question_bank_filters
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_validation_middleware = decorator_from_middleware_with_args(
    SerializerValidationMiddleware)

check_access_permission_middleware = decorator_from_middleware_with_args(
    CollaborationAccessMiddleware)
authorizator = decorator_from_middleware_with_args(
    AuthorizatorMiddleware)
check_context_institute_accessibility_on_question_bank_middleware = decorator_from_middleware_with_args(
    SubscriptionAccessMiddleware)


@method_decorator(authorizator(accept_user_token=True), name='post')
@method_decorator(serializer_validation_middleware(question_serializers.CreateQuestionApiInputSerializer), name='post')
@method_decorator(
    check_access_permission_middleware(
        [dr.__PERMISSION_WRITE_QUESTION__], QuestionBank, 'id', 'question_bank_id'),
    name='post')
class CreateQuestion(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        keywords = request.middleware_serializer_data.get('keywords')
        question_text = request.middleware_serializer_data.get('question_text')
        format = request.middleware_serializer_data.get(
            'format')
        question_type = request.middleware_serializer_data.get('question_type')
        input_rules = request.middleware_serializer_data.get('input_rules')
        tags = request.middleware_serializer_data.get('tags')
        question_bank = request.middleware_model_record

        solution = request.middleware_serializer_data.get(
            'solution')

        choices = request.middleware_serializer_data.get(
            'choices')
        correct_choices = request.middleware_serializer_data.get(
            'correct_choices')
        meta_data = request.middleware_serializer_data.get('meta_data')

        # ########################## Block 2 ################################
        # TODO: check user limitations to create Question
        if not check_institute_limitations(Question, request.middleware_institute, question_bank=question_bank):
            return MyResponse(request, {'status': 'error', 'message': 'Upgrade Required'},
                              status=status.HTTP_426_UPGRADE_REQUIRED)

        # ########################## Block 3 ################################
        question = Question.objects.create(
            question_bank=question_bank,
            keywords=keywords,
            question_text=question_text,
            format=format,
            question_type=question_type,
            input_rules=input_rules,
            choices=choices,
            correct_choices=correct_choices,
            tags=tags,
            solution=solution,
            auto_correctable=question_type == QUESTION_TYPE_MULTIPLE_CHOICE,
            meta_data=meta_data
        )
        # TODO This part was added deliberately
        # TODO to test the problem of choices order mismatch
        # TODO it causes the test_create_multiple_choice_question_successful to fail and it is expected to fail
        # TODO until the order bug is fixed
        question = Question.objects.get(pk=question.pk)
        # ########################## Block 4 ################################
        question_bank.filters = modify_question_bank_filters(
            filters=question_bank.filters, tags=tags, add_missed_values=True, stock_step=1)
        question_bank.tags = modify_question_bank_filters(
            filters=question_bank.tags, tags=tags, add_missed_values=True, add_missed_keys=True, stock_step=1)

        question_bank.questions_count += 1
        question_bank.save()

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


@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.CreateQuestionBulkApiInputSerializer), name='post')
@method_decorator(
    check_access_permission_middleware(
        [dr.__PERMISSION_WRITE_QUESTION__], QuestionBank, 'id', 'question_bank_id'),
    name='post')
class BulkCreateQuestion(APIView):

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

        # ########################## Block 2 ################################
        # TODO: check user limitations to create Question
        if not check_institute_limitations(Question, request.middleware_institute, question_bank=question_bank):
            return MyResponse(request, {'status': 'error', 'message': 'Upgrade Required'},
                              status=status.HTTP_426_UPGRADE_REQUIRED)

        questions = [Question(question_bank=question_bank,
                              keywords=question['keywords'] if 'keywords' in question else None,
                              question_text=question['question_text'],
                              format=question['format'],
                              question_type=question['question_type'],
                              input_rules=question['input_rules'],
                              choices=question['choices'],
                              correct_choices=question['correct_choices'],
                              tags=question['tags'],
                              solution=question.get('solution'),
                              meta_data=question.get('meta_data'),
                              auto_correctable=question['question_type'] == QUESTION_TYPE_MULTIPLE_CHOICE) for question in questions]

        Question.objects.bulk_create(questions)

        # ########################## Block 4 ################################
        for question in questions:
            question_bank.filters = modify_question_bank_filters(
                filters=question_bank.filters, tags=question.tags, add_missed_values=True, stock_step=1)
            question_bank.tags = modify_question_bank_filters(
                filters=question_bank.tags, tags=question.tags, add_missed_values=True, add_missed_keys=True, stock_step=1)

        question_bank.questions_count += len(questions)
        question_bank.save()

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


@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.EditQuestionApiInputSerializer), name='post')
@method_decorator(check_access_permission_middleware([dr.__PERMISSION_WRITE_QUESTION__], Question, 'id', 'question_id'),
                  name='post')
class EditQuestion(APIView):

    def post(self, request):

        # ########################## Block 1 ################################
        # receiving user inputs from serializer
        keywords = request.middleware_serializer_data.get('keywords')
        question_text = request.middleware_serializer_data.get('question_text')
        format = request.middleware_serializer_data.get(
            'format')
        question_type = request.middleware_serializer_data.get('question_type')
        input_rules = request.middleware_serializer_data.get('input_rules')
        tags = request.middleware_serializer_data.get('tags')

        solution = request.middleware_serializer_data.get(
            'solution')

        choices = request.middleware_serializer_data.get(
            'choices')
        correct_choices = request.middleware_serializer_data.get(
            'correct_choices')
        meta_data = request.middleware_serializer_data.get(
            'meta_data')

        question_record = request.middleware_model_record

        if question_record.archived:
            return MyResponse(request, {'status': 'error', 'message': 'An archived question cannot be edited.'},
                              status=status.HTTP_400_BAD_REQUEST)

        ########################### Block 3 ################################
        previous_tags = question_record.tags

        question_record.keywords = keywords,
        question_record.question_text = question_text
        question_record.format = format
        question_record.question_type = question_type
        question_record.input_rules = input_rules
        question_record.choices = choices
        question_record.correct_choices = correct_choices
        question_record.tags = tags
        question_record.solution = solution
        question_record.auto_correctable = question_type == QUESTION_TYPE_MULTIPLE_CHOICE
        question_record.meta_data = meta_data
        question_record.save()

        # ########################## Block 4 ################################
        question_bank = question_record.question_bank
        question_bank.filters = modify_question_bank_filters(
            filters=question_bank.filters, tags=previous_tags, add_missed_values=False, stock_step=-1)
        question_bank.tags = modify_question_bank_filters(
            filters=question_bank.tags, tags=previous_tags, add_missed_values=False, add_missed_keys=True, stock_step=-1)

        question_bank.filters = modify_question_bank_filters(
            filters=question_bank.filters, tags=tags, add_missed_values=True, stock_step=1)
        question_bank.tags = modify_question_bank_filters(
            filters=question_bank.tags, tags=tags, add_missed_values=True, add_missed_keys=True, stock_step=1)

        question_bank.save()

        return MyResponse(request,
                          {'status': 'ok', 'message': 'Successful', 'data': {
                              'question': question_serializers.QuestionModelSerializer(
                                  question_record).data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.DeleteQuestionApiInputSerializer), name='post')
@method_decorator(check_access_permission_middleware([dr.__PERMISSION_WRITE_QUESTION__], Question, 'id', 'question_id'),
                  name='post')
class ArchiveQuestion(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        # get question from middleware and set it's deleted to True and also decrease it's questions_count by 1
        question = request.middleware_model_record
        if not question.archived:
            question.archived = True
            question.save()

            question_bank = question.question_bank

            question_bank.filters = modify_question_bank_filters(
                filters=question_bank.filters, tags=question.tags, add_missed_values=False, stock_step=-1)
            question_bank.tags = modify_question_bank_filters(
                filters=question_bank.tags, tags=question.tags, add_missed_values=False, add_missed_keys=True, stock_step=-1)

            question_bank.questions_count = max(
                0, question_bank.questions_count - 1)

            question_bank.save()

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


@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.QuestionsListApiInputSerializer), name='post')
@method_decorator(
    check_access_permission_middleware([dr.__PERMISSION_READ_QUESTIONBANK__, dr.__PERMISSION_READ_QUESTION__],
                                       Institute, 'id',
                                       'context_institute_id'), name='post')
@method_decorator(
    check_context_institute_accessibility_on_question_bank_middleware(
        QuestionBank, 'id', 'question_bank_id'),
    name='post')
class ListQuestions(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')
        archived_filter = request.middleware_serializer_data.get(
            'archived_filter')
        tag_filters = request.middleware_serializer_data.get('tag_filters')
        old_tag_filters = request.middleware_serializer_data.get(
            'old_tag_filters')
        question_type = request.middleware_serializer_data.get('question_type')
        sort_by = request.middleware_serializer_data.get('sort_by')
        sort_by_attr = sort_by_translator(sort_by)
        auto_correctable_filter = request.middleware_serializer_data.get(
            'auto_correctable_filter')
        # ########################## Block 2 ################################
        question_bank = request.middleware_question_bank
        questions = Question.objects.filter(question_bank=question_bank)
        has_complex_query = False
        if old_tag_filters:
            has_complex_query = True
            query = reduce(and_, (reduce(or_, (Q(tags__contains=item)
                                               for item in [{child["key"]: child["value"]} for child in group]))
                                  for group in old_tag_filters if group))
            questions = questions.filter(query)
        if tag_filters:
            has_complex_query = True
            query = get_query_from_tag_filters_list(tag_filters)
            questions = questions.filter(query)

        if archived_filter == 'exclude':
            has_complex_query = True
            questions = questions.filter(archived=False)
        elif archived_filter == 'only':
            has_complex_query = True
            questions = questions.filter(archived=True)

        if q is not None:
            has_complex_query = True
            questions = questions.filter(keywords__icontains=q)
        if question_type:
            has_complex_query = True
            questions = questions.filter(question_type=question_type)

        if auto_correctable_filter == 'only':
            has_complex_query = True
            questions = questions.filter(auto_correctable=True)
        elif auto_correctable_filter == 'exclude':
            has_complex_query = True
            questions = questions.filter(auto_correctable=False)

        if has_complex_query:
            total_count = questions.count()
        else:
            total_count = question_bank.questions_count

        questions = questions.order_by(sort_by_attr).select_related(
            'question_bank')[skip:skip + take]
        questions_data = question_serializers.QuestionModelSerializer(
            questions, many=True).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'questions': questions_data, 'total_count': total_count}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.QuestionsGetApiInputSerializer), name='post')
@method_decorator(
    check_access_permission_middleware([dr.__PERMISSION_READ_QUESTIONBANK__, dr.__PERMISSION_READ_QUESTION__],
                                       Institute, 'id',
                                       'context_institute_id'), name='post')
@method_decorator(
    check_context_institute_accessibility_on_question_bank_middleware(
        Question, 'id', 'question_id'),
    name='post')
class GetQuestion(APIView):

    def post(self, request):
        # ########################## Block 1 ################################
        question = request.middleware_question

        question_data = question_serializers.QuestionModelSerializer(
            question).data
        return MyResponse(request,
                          {'status': 'ok', 'message': 'Successful', 'data': {
                              'question': question_data}},
                          status=status.HTTP_200_OK)


# BULK EDIT
@method_decorator(authorizator(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(question_serializers.EditQuestionBulkApiInputSerializer), name='post')
@method_decorator(check_access_permission_middleware([dr.__PERMISSION_WRITE_QUESTION__], QuestionBank, 'id', 'question_bank_id'),
                  name='post')
class EditQuestionBulk(APIView):

    def post(self, request):
        question_bank_record = request.middleware_model_record
        commit = request.middleware_serializer_data.get('commit')
        # remove, update, add
        action = request.middleware_serializer_data.get('action')

        current_key = request.middleware_serializer_data.get('current_key')
        current_value = request.middleware_serializer_data.get('current_value')
        incoming_key = request.middleware_serializer_data.get('incoming_key')
        incoming_value = request.middleware_serializer_data.get(
            'incoming_value')
        
        query = Question.objects.filter(
            question_bank=question_bank_record)
        if current_key and current_value:
            query = query.filter(tags__contains={current_key: current_value})
        elif current_key:
             query = query.filter(tags__has_key=current_key)
        elif current_value:
             query = query.filter(tags__values__contains=[current_value])
        query_count = query.count()
        if action == 'add' and incoming_key and incoming_value:
            if commit:
                for question in query:
                    question.tags[incoming_key]=incoming_value
                Question.objects.bulk_update(query, ['tags'])
                return MyResponse(request,
                                {'status': 'ok', 'message': 'Success'},
                                status=status.HTTP_200_OK)
            else:
                msg = f'برچسب {incoming_key or "*"} با مقدار {incoming_value or "*"} به {query_count} سوال اضافه خواهد شد'
                return MyResponse(request,
                                {'status': 'ok', 'message': msg},
                                status=status.HTTP_200_OK)
        elif action == 'update' and incoming_key and current_key:
            if commit:
                for question in query:
                    current_value = question.tags[current_key]
                    del question.tags[current_key]

                    question.tags[incoming_key]=incoming_value or current_value
                    
                    
                Question.objects.bulk_update(query, ['tags'])
                return MyResponse(request,
                                {'status': 'ok', 'message': 'Success'},
                                status=status.HTTP_200_OK)
            else:
                msg = f'برچسب {current_key or "*"} با مقدار {current_value or "*"} از روی {query_count} سوال حذف و برچب {incoming_key or "*"} با مقدار {incoming_value or "*"} به سوالات مذکور اضافه خواهد شد'
                return MyResponse(request,
                                {'status': 'ok', 'message': msg},
                                status=status.HTTP_200_OK)
        elif action == 'remove' and current_key:
            if commit:
                for question in query:
                    del question.tags[current_key]
                    
                Question.objects.bulk_update(query, ['tags'])
                return MyResponse(request,
                                {'status': 'ok', 'message': 'Success'},
                                status=status.HTTP_200_OK)
            

            else:
                msg = f'برچسب {current_key or "*"} با مقدار {current_value or "*"} از روی {query_count} سوال حذف خواهد شد'
                return MyResponse(request,
                                {'status': 'ok', 'message': msg},
                                status=status.HTTP_200_OK)
        else:
            return MyResponse(request,
                              {'status': 'error', 'message': 'Bad Request'},
                              status=status.HTTP_400_BAD_REQUEST)
