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

from authenticate.middleware import AuthorizatorMiddleware
from collaborators import defined_roles as dr
from collaborators.middleware import CollaborationAccessMiddleware
from exam.models import Exam
from institute.models import Institute, InstituteStudent
from institute.serializers import InstituteSerializerForExamAndInstituteAndQuestionBank
from payment.models import UserBalanceHistory
from question.models import QUESTION_TYPE_MULTIPLE_CHOICE, QUESTION_TYPE_DESCRIPTIVE, Question, QUESTION_FORMAT_SERIALIZER_CHOICES
from quiz import conf
from quiz.models import Quiz, QuizSheet, QUIZ_SHEET_STATE_FINISHED, QUIZ_SHEET_STATE_SCORES_PUBLISHED, QuizTemplate, \
    QuestionScore, QUIZ_SHEET_STATE_IN_PROGRESS, QUIZ_SHEET_STATE_NOT_ATTENDED, QUIZ_TYPE_SURVEY
from quiz.serializers import ArchiveQuizInputSerializer, CreateQuizInputSerializer, EditQuizInputSerializer, \
    GetQuizInputSerializer, QuizListInputSerializer, QuizModelSerializer, QuizGateWayInputSerializer, QuizEnterInputSerializer, \
    QuizSheetModelForEnterSerializer, GenerateQuizSheetInputValidator, SetAnswerInputSerializer, FinishQuizSheetInputSerializer, \
    InvoiceInputSerializer, CreateQuizTemplateInputSerializer, EditQuizTemplateInputSerializer, QuizTemplateModelSerializer, \
    QuizTemplateMarketInputSerializer, GetQuizSheetForInstituteSerializer, VerifyQuizSheetInputValidator, \
    QuizSheetCorrectionAddInputValidator, GetQuizStatInputSerializer, QuizSheetModelForStatSerializer, \
    QuizSheetForInstituteModelSerializer, QuizGetModelSerializer, QuizSheetModelSerializerForHistory, \
    QuizTemplateListInputSerializer, GetQuizSheetListInputSerializer, GetQuizSheetForListSerializer, \
    GetQuizTemplateInputSerializer, ArchiveQuizTemplateInputSerializer, QuizSheetModelForReportCardSerializer, \
    QuizSheetReportInputSerializer, QuizReportCardTestInputSerializer, GetQuizSheetAttendedListInputSerializer, \
    QuizSheetAttendedGetInputSerializer, QuizSheetGetInputSerializer, GetQuizIdFromExamIdInputValidator
from quiz.utils import create_quiz_sheet_helper
from utils.myresponse import MyResponse
from utils.utils import compress_image, sort_by_translator, logs_adder
from wikiazma.middleware import SerializerValidationMiddleware
from .blueprint.blueprint import Blueprint
from .blueprint.rule import Rule
from .middleware import EntranceValidatorMiddleware, QuizSheetStatusValidationMiddleware, QuizPurchaseHandlerMiddleware
from .utils import generate_report_card_helper, auto_correct_helper, price_calculator_helper, quiz_purchase_handler

authorizator_decorator = decorator_from_middleware_with_args(AuthorizatorMiddleware)

serializer_decorator = decorator_from_middleware_with_args(SerializerValidationMiddleware)

permission_decorator = decorator_from_middleware_with_args(CollaborationAccessMiddleware)

entrance_validator_middleware = decorator_from_middleware_with_args(EntranceValidatorMiddleware)

quiz_purchase_middleware = decorator_from_middleware_with_args(QuizPurchaseHandlerMiddleware)

quiz_sheet_status_validation = decorator_from_middleware_with_args(QuizSheetStatusValidationMiddleware)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(QuizListInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], Institute, 'id', 'context_institute_id'), name='post')
class QuizList(APIView):
    def post(self, request):
        # Receive data from serializer
        q = request.middleware_serializer_data.get('q')
        qid = request.middleware_serializer_data.get('qid')
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))
        expired_filter = request.middleware_serializer_data.get('expired_filter')
        reanswerable_filter = request.middleware_serializer_data.get('reanswerable_filter')
        shuffle_questions_filter = request.middleware_serializer_data.get('shuffle_questions_filter')
        shuffle_choices_filter = request.middleware_serializer_data.get('shuffle_choices_filter')
        single_question_at_a_time_filter = request.middleware_serializer_data.get('single_question_at_a_time_filter')
        archived_filter = request.middleware_serializer_data.get('archived_filter')
        ########################### Block 2 ################################
        quiz_queryset = Quiz.objects.filter(institute=request.middleware_institute)

        if qid:
            # if quiz_id was sent, the other query filters are useless
            quiz_queryset = quiz_queryset.filter(id=qid)
        else:
            # name query
            if q:
                quiz_queryset = quiz_queryset.filter(name__icontains=q)

            # expired_filter
            if expired_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(Q(end__gt=timezone.now()) | Q(end__isnull=True))
            elif expired_filter == 'only':
                quiz_queryset = quiz_queryset.filter(end__lte=timezone.now())

            # reanswerable_filter
            if reanswerable_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(reanswerable=False)
            elif reanswerable_filter == 'only':
                quiz_queryset = quiz_queryset.filter(reanswerable=True)

            # shuffle_questions_filter
            if shuffle_questions_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(shuffle_questions=False)
            elif shuffle_questions_filter == 'only':
                quiz_queryset = quiz_queryset.filter(shuffle_questions=True)

            # shuffle_choices_filter
            if shuffle_choices_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(shuffle_choices=False)
            elif shuffle_choices_filter == 'only':
                quiz_queryset = quiz_queryset.filter(shuffle_choices=True)

            # single_question_at_a_time_filter
            if single_question_at_a_time_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(single_question_at_a_time=False)
            elif single_question_at_a_time_filter == 'only':
                quiz_queryset = quiz_queryset.filter(single_question_at_a_time=True)

            # archived_filter
            if archived_filter == 'exclude':
                quiz_queryset = quiz_queryset.filter(archived=False)
            elif archived_filter == 'only':
                quiz_queryset = quiz_queryset.filter(archived=True)
        # ########################## Block 3 ################################

        total_count = quiz_queryset.count()
        quiz_queryset = quiz_queryset.order_by(sort_by)[skip:skip + take]

        quiz_data = QuizModelSerializer(quiz_queryset, many=True).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'current_date_time': str(timezone.now()), 'total_count': total_count,
                                             'quizzes': quiz_data}}, status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(GetQuizInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], Quiz, 'pk', 'id'), name='post')
class QuizGet(APIView):
    def post(self, request):
        quiz = request.middleware_model_record

        quiz_data = QuizGetModelSerializer(quiz, context={"with_blueprint": True}).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'quiz': quiz_data, 'current_date_time': timezone.now()}}, status=status.HTTP_200_OK)


# TODO quiz stat was moving toward being quiz/sheet/list API. remove skip, take, filters
@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(GetQuizStatInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], Quiz, 'pk', 'quiz_id'), name='post')
class QuizStat(APIView):
    def post(self, request):
        quiz = request.middleware_model_record
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')

        name_filter = request.middleware_serializer_data.get('name_filter')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))

        sheets = QuizSheet.objects.filter(quiz=quiz)

        if name_filter:
            sheets = sheets.filter(
                Q(user__first_name__contains=name_filter) |
                Q(user__last_name__contains=name_filter) |
                Q(institute_student__first_name__contains=name_filter) |
                Q(institute_student__last_name__contains=name_filter)
            )

        total_count = sheets.count()
        sheets_queryset = sheets.order_by(sort_by)[skip:skip + take]

        for quiz_sheet in sheets:
            error_status, message = quiz_sheet.can_be_continued_state()
            if error_status is not None and quiz_sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and quiz_sheet.state != QUIZ_SHEET_STATE_FINISHED:
                quiz_sheet_finish_helper(quiz_sheet)

        quiz_data = QuizModelSerializer(quiz, context={"with_blueprint": True}).data
        sheets_data = QuizSheetModelForStatSerializer(sheets_queryset, many=True).data

        return MyResponse(request,
                          {'status': 'ok', 'message': 'Successful',
                           'data': {'quiz': quiz_data, 'quiz_sheets': sheets_data, 'total_count': total_count,
                                    'current_date_time': timezone.now()}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(GetQuizSheetListInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM_PAGE__], Quiz, 'pk', 'quiz_id'), name='post')
class QuizSheetList(APIView):
    def post(self, request):
        quiz = request.middleware_model_record
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')

        name_filter = request.middleware_serializer_data.get('name_filter')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))

        sheets = QuizSheet.objects.filter(quiz=quiz)

        if name_filter:
            sheets = sheets.filter(
                Q(user__first_name__contains=name_filter) |
                Q(user__last_name__contains=name_filter) |
                Q(institute_student__first_name__contains=name_filter) |
                Q(institute_student__last_name__contains=name_filter)
            )

        total_count = sheets.count()
        sheets_queryset = sheets.order_by(sort_by)[skip:skip + take]

        for quiz_sheet in sheets:
            error_status, message = quiz_sheet.can_be_continued_state()
            if error_status is not None and quiz_sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and quiz_sheet.state != QUIZ_SHEET_STATE_FINISHED:
                quiz_sheet_finish_helper(quiz_sheet)

        quiz_data = QuizModelSerializer(quiz, context={"with_blueprint": True}).data
        sheets_data = GetQuizSheetForListSerializer(sheets_queryset, many=True).data

        return MyResponse(request,
                          {'status': 'ok', 'message': 'Successful',
                           'data': {'quiz': quiz_data, 'quiz_sheets': sheets_data, 'total_count': total_count,
                                    'current_date_time': timezone.now()}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(QuizSheetGetInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM_PAGE__], QuizSheet, 'pk', 'quiz_sheet_id'), name='post')
class QuizSheetGet(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_model_record

        quiz_sheet_data = QuizSheetModelForReportCardSerializer(quiz_sheet).data

        data = {'quiz_sheet': quiz_sheet_data}

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


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(GetQuizSheetAttendedListInputSerializer), name='post')
class QuizSheetAttendedList(APIView):
    def post(self, request):
        user = request.middleware_user

        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')

        name_filter = request.middleware_serializer_data.get('name_filter')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))

        sheets = QuizSheet.objects.filter(user=user)

        if name_filter:
            sheets = sheets.filter(
                Q(user__first_name__contains=name_filter) |
                Q(user__last_name__contains=name_filter) |
                Q(institute_student__first_name__contains=name_filter) |
                Q(institute_student__last_name__contains=name_filter)
            )

        total_count = sheets.count()
        sheets_queryset = sheets.order_by(sort_by)[skip:skip + take]

        for quiz_sheet in sheets:
            error_status, message = quiz_sheet.can_be_continued_state()
            if error_status is not None and quiz_sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and quiz_sheet.state != QUIZ_SHEET_STATE_FINISHED:
                quiz_sheet_finish_helper(quiz_sheet)

        sheets_data = GetQuizSheetForListSerializer(sheets_queryset, many=True).data

        return MyResponse(request,
                          {'status': 'ok', 'message': 'Successful',
                           'data': {'quiz_sheets': sheets_data, 'total_count': total_count, 'current_date_time': timezone.now()}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(QuizSheetAttendedGetInputSerializer), name='post')
class QuizSheetAttendedGet(APIView):

    def post(self, request):
        user = request.middleware_user
        quiz_sheet_id = request.middleware_serializer_data.get('quiz_sheet_id')

        try:
            quiz_sheet = QuizSheet.objects.get(pk=quiz_sheet_id, user=user)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz Sheet not found'}, status=status.HTTP_404_NOT_FOUND)

        quiz_sheet_data = QuizSheetModelForEnterSerializer(quiz_sheet).data

        data = {'quiz_sheet': quiz_sheet_data}

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


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(CreateQuizInputSerializer), name='post')
@method_decorator(
    permission_decorator([dr.__PERMISSION_WRITE_EXAM__, dr.__PERMISSION_READ_QUESTION__, dr.__PERMISSION_READ_QUESTIONBANK__],
                         Institute, 'id', 'context_institute_id'), name='post')
class CreateQuiz(APIView):
    def post(self, request):
        context_institute = request.middleware_institute
        # Receive data from serializer
        name = request.middleware_serializer_data.get('name')
        rule_dict = request.middleware_serializer_data.get('rule')

        single_question_at_a_time = request.middleware_serializer_data.get('single_question_at_a_time')
        public = request.middleware_serializer_data.get('public')
        reanswerable = request.middleware_serializer_data.get('reanswerable')
        quiz_type = request.middleware_serializer_data.get('quiz_type')

        shuffle_questions = request.middleware_serializer_data.get('shuffle_questions')
        shuffle_choices = request.middleware_serializer_data.get('shuffle_choices')
        apply_negative_scores = request.middleware_serializer_data.get('apply_negative_scores')

        poster = request.middleware_serializer_data.get('poster')
        start = request.middleware_serializer_data.get('start')
        end = request.middleware_serializer_data.get('end')
        duration = request.middleware_serializer_data.get('duration')
        description = request.middleware_serializer_data.get('description')
        theme = request.middleware_serializer_data.get('theme')

        price = request.middleware_serializer_data.get('price')
        finish_message = request.middleware_serializer_data.get('finish_message')

        rule = Rule.from_dict(rule_dict)
        # this will raise error if permission was not granted
        rule.permission_checker(context_institue=context_institute)

        blueprint = Blueprint.from_rule(rule=rule)

        quiz = Quiz.objects.create(institute=context_institute, name=name,
                                   single_question_at_a_time=True if not reanswerable else single_question_at_a_time,
                                   reanswerable=reanswerable, quiz_type=quiz_type, shuffle_questions=shuffle_questions,
                                   shuffle_choices=shuffle_choices, apply_negative_scores=apply_negative_scores, start=start,
                                   end=end, public=public, duration=duration, questions_count=blueprint.questions_count(),
                                   description=description, _blueprint=blueprint.to_dict(), theme=theme, price=price,
                                   finish_message=finish_message, creator=request.middleware_user,
                                   poster=compress_image(poster,
                                                         max_side_size=conf.quiz_poster_compress_side_size) if poster else None,
                                   poster_thumbnail=compress_image(poster,
                                                                   max_side_size=conf.quiz_poster_thumb_compress_side_size) if poster else None)

        quiz_data = QuizModelSerializer(quiz, context={"with_blueprint": True}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz': quiz_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(EditQuizInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_WRITE_EXAM__], Quiz, 'pk', 'quiz_id'), name='post')
class EditQuiz(APIView):

    def post(self, request):

        context_institute = request.middleware_institute
        quiz = request.middleware_model_record

        # Receive data from serializer
        name = request.middleware_serializer_data.get('name')
        rule_dict = request.middleware_serializer_data.get('rule')

        single_question_at_a_time = request.middleware_serializer_data.get('single_question_at_a_time')
        public = request.middleware_serializer_data.get('public')
        reanswerable = request.middleware_serializer_data.get('reanswerable')
        quiz_type = request.middleware_serializer_data.get('quiz_type')

        shuffle_questions = request.middleware_serializer_data.get('shuffle_questions')
        shuffle_choices = request.middleware_serializer_data.get('shuffle_choices')
        apply_negative_scores = request.middleware_serializer_data.get('apply_negative_scores')

        poster = request.middleware_serializer_data.get('poster')
        remove_poster = request.middleware_serializer_data.get('remove_poster')
        start = request.middleware_serializer_data.get('start')
        end = request.middleware_serializer_data.get('end')
        duration = request.middleware_serializer_data.get('duration')
        description = request.middleware_serializer_data.get('description')
        theme = request.middleware_serializer_data.get('theme')

        price = request.middleware_serializer_data.get('price')
        finish_message = request.middleware_serializer_data.get('finish_message')

        if rule_dict:
            rule = Rule.from_dict(rule_dict)
            # this will raise error if permission was not granted
            rule.permission_checker(context_institue=context_institute)

            blueprint = Blueprint.from_rule(rule=rule)
            quiz._blueprint = blueprint.to_dict()
            quiz.questions_count = blueprint.questions_count()

        quiz.name = name
        quiz.single_question_at_a_time = True if not reanswerable else single_question_at_a_time
        quiz.public = public
        quiz.reanswerable = reanswerable
        quiz.quiz_type = quiz_type
        quiz.shuffle_questions = shuffle_questions
        quiz.shuffle_choices = shuffle_choices
        quiz.apply_negative_scores = apply_negative_scores
        quiz.start = start
        quiz.end = end
        quiz.duration = duration

        quiz.description = description
        quiz.last_editor = request.middleware_user

        quiz.theme = theme
        quiz.price = price
        quiz.finish_message = finish_message
        if remove_poster and quiz.poster:
            quiz.poster = None
            quiz.poster_thumbnail = None
        elif poster:
            quiz.poster = compress_image(poster, max_side_size=conf.quiz_poster_compress_side_size)
            quiz.poster_thumbnail = compress_image(poster, max_side_size=conf.quiz_poster_thumb_compress_side_size)
        quiz.save()

        quiz_data = QuizModelSerializer(quiz, context={"with_blueprint": True}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz': quiz_data}},
                          status=status.HTTP_200_OK)


def get_report_card_group(report_card, book_name):
    for book in report_card['groups']:
        if book['name'] == book_name:
            return book


def get_report_card_group_t_helper(report_card, book_name):
    found_book = get_report_card_group(report_card, book_name)
    if found_book is None:
        return 0
    return found_book['t']


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(QuizReportCardTestInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], Quiz, 'pk', 'quiz_id'), name='post')
class QuizReportCardTest(APIView):

    def post(self, request):

        context_institute = request.middleware_institute
        quiz = request.middleware_model_record

        quiz_sheets = list(QuizSheet.objects.filter(quiz=quiz, state=QUIZ_SHEET_STATE_SCORES_PUBLISHED))
        blueprint = Blueprint.from_dict(quiz._blueprint)

        if len(quiz_sheets) == 0:
            return MyResponse(request, {'status': 'ok', 'message': 'No Quiz Sheet found', 'data': {}},
                              status=status.HTTP_200_OK)

        books = [blueprint_group.name for blueprint_group in blueprint.groups]

        # calculating t for all books
        for i in range(len(books)):
            book = books[i]
            percentages = []
            for quiz_sheet in quiz_sheets:
                found_book = get_report_card_group(quiz_sheet.report_card, book)
                if found_book is None:
                    continue
                percentages.append(found_book['percent_with_negatives'])

            if len(percentages) == 0:
                books.remove(book)
                i -= 1
                break

            mean = sum(percentages) / len(percentages)
            variance = sum([((x - mean) ** 2) for x in percentages]) / len(percentages)
            stddev = variance ** 0.5

            for quiz_sheet in quiz_sheets:
                found_book = get_report_card_group(quiz_sheet.report_card, book)
                if found_book is None:
                    continue
                found_book['mean'] = mean
                found_book['standard_deviation'] = stddev
                if stddev != 0:
                    found_book['z'] = (found_book['percent_with_negatives'] - mean) / stddev
                else:
                    found_book['z'] = 0

                found_book['t'] = 1000 * found_book['z'] + 5000

        if len(books) == 0:
            return MyResponse(request, {'status': 'ok', 'message': 'No Book Found', 'data': {}},
                              status=status.HTTP_200_OK)

        # calculating t for overall
        for quiz_sheet in quiz_sheets:
            total_t = 0
            total_factor = 0

            for book in quiz_sheet.report_card['groups']:
                # TODO
                # total_t += book['t'] * book['factor']
                total_t += book['t'] * 1
                # total_factor += book['factor']
                total_factor += 1

            quiz_sheet.report_card['overall']['t'] = total_t / total_factor

        # calculating rank for books
        for book in books:
            quiz_sheets.sort(reverse=True, key=lambda quiz_sheet: get_report_card_group_t_helper(quiz_sheet.report_card, book))

            country_report = {'t': 100000, 'count': 0, 'rank': 0}
            # grade_report = {}

            for quiz_sheet in quiz_sheets:
                found_book = get_report_card_group(quiz_sheet.report_card, book)
                if found_book is None:
                    continue

                country_report['count'] += 1
                if found_book['t'] != country_report['t']:
                    country_report['rank'] = country_report['count']
                    country_report['t'] = found_book['t']

                found_book['country_rank'] = country_report['rank']

                # if report_card['sub_group'] not in grade_report:
                #     grade_report[report_card['sub_group']] = {'t': 100000, 'count': 0, 'rank': 0}

                # grade_report[report_card['sub_group']]['count'] += 1
                # if report_card['books'][index]['t'] != grade_report[report_card['sub_group']]['t']:
                #     grade_report[report_card['sub_group']]['rank'] = grade_report[report_card['sub_group']]['count']
                #     grade_report[report_card['sub_group']]['t'] = report_card['books'][index]['t']
                #
                # report_card['books'][index]['group_rank'] = grade_report[report_card['sub_group']]['rank']

            for quiz_sheet in quiz_sheets:
                found_book = get_report_card_group(quiz_sheet.report_card, book)
                if found_book is None:
                    continue
                found_book['country_total_students'] = country_report['count']
                # quiz_sheet.report_card['grade_total_students'] = grade_report[report_card['sub_group']]['count']

        # calculating rank for overall and country and grade
        quiz_sheets.sort(reverse=True, key=lambda quiz_sheet: quiz_sheet.report_card['overall']['t'])
        country_report = {'t': 100000, 'count': 0, 'rank': 0}
        # grade_report = {}
        for quiz_sheet in quiz_sheets:
            country_report['count'] += 1
            if quiz_sheet.report_card['overall']['t'] != country_report['t']:
                country_report['rank'] = country_report['count']
                country_report['t'] = quiz_sheet.report_card['overall']['t']

            quiz_sheet.report_card['overall']['country_rank'] = country_report['rank']

            # if report_card['sub_group'] not in grade_report:
            #     grade_report[report_card['sub_group']] = {'t': 100000, 'count': 0, 'rank': 0}
            #
            # grade_report[report_card['sub_group']]['count'] += 1
            # if report_card['overall']['t'] != grade_report[report_card['sub_group']]['t']:
            #     grade_report[report_card['sub_group']]['rank'] = grade_report[report_card['sub_group']]['count']
            #     grade_report[report_card['sub_group']]['t'] = report_card['overall']['t']
            #
            # report_card['overall']['group_rank'] = grade_report[report_card['sub_group']]['rank']

        for quiz_sheet in quiz_sheets:
            quiz_sheet.report_card['overall']['country_total_students'] = country_report['count']
            # quiz_sheet.report_card['overall']['grade_total_students'] = grade_report[report_card['sub_group']]['count']

            quiz_sheet.save()

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


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(ArchiveQuizInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_WRITE_EXAM__], Quiz, 'pk', 'quiz_id'), name='post')
class ArchiveQuiz(APIView):

    def post(self, request):
        quiz = request.middleware_model_record
        quiz.archived = True
        quiz.save()

        quiz_data = QuizModelSerializer(quiz, context={"with_blueprint": False}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz': quiz_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(QuizTemplateListInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], Institute, 'id', 'context_institute_id'), name='post')
class QuizTemplateList(APIView):
    def post(self, request):
        # Receive data from serializer
        q = request.middleware_serializer_data.get('q')
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))
        reanswerable_filter = request.middleware_serializer_data.get('reanswerable_filter')
        shuffle_questions_filter = request.middleware_serializer_data.get('shuffle_questions_filter')
        shuffle_choices_filter = request.middleware_serializer_data.get('shuffle_choices_filter')
        single_question_at_a_time_filter = request.middleware_serializer_data.get('single_question_at_a_time_filter')
        archived_filter = request.middleware_serializer_data.get('archived_filter')
        ########################### Block 2 ################################
        quiz_queryset = QuizTemplate.objects.filter(institute=request.middleware_institute)

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

        # reanswerable_filter
        if reanswerable_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(reanswerable=False)
        elif reanswerable_filter == 'only':
            quiz_queryset = quiz_queryset.filter(reanswerable=True)

        # shuffle_questions_filter
        if shuffle_questions_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(shuffle_questions=False)
        elif shuffle_questions_filter == 'only':
            quiz_queryset = quiz_queryset.filter(shuffle_questions=True)

        # shuffle_choices_filter
        if shuffle_choices_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(shuffle_choices=False)
        elif shuffle_choices_filter == 'only':
            quiz_queryset = quiz_queryset.filter(shuffle_choices=True)

        # single_question_at_a_time_filter
        if single_question_at_a_time_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(single_question_at_a_time=False)
        elif single_question_at_a_time_filter == 'only':
            quiz_queryset = quiz_queryset.filter(single_question_at_a_time=True)

        # archived_filter
        if archived_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(archived=False)
        elif archived_filter == 'only':
            quiz_queryset = quiz_queryset.filter(archived=True)
        # ########################## Block 3 ################################

        total_count = quiz_queryset.count()
        quiz_queryset = quiz_queryset.order_by(sort_by)[skip:skip + take]

        quiz_data = QuizTemplateModelSerializer(quiz_queryset, many=True).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'current_date_time': timezone.now(), 'total_count': total_count,
                                             'quiz_templates': quiz_data}}, status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_token=False, accept_server_api_key=False),
                  name='post')
@method_decorator(serializer_decorator(GetQuizTemplateInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM_TEMPLATE__], QuizTemplate, 'pk', 'id'), name='post')
class QuizTemplateGet(APIView):
    def post(self, request):
        quiz_template = request.middleware_model_record

        quiz_data = QuizTemplateModelSerializer(quiz_template, context={"with_blueprint": True}).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'quiz_template': quiz_data, 'current_date_time': timezone.now()}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(CreateQuizTemplateInputSerializer), name='post')
@method_decorator(
    permission_decorator([dr.__PERMISSION_WRITE_EXAM__, dr.__PERMISSION_READ_QUESTION__, dr.__PERMISSION_READ_QUESTIONBANK__],
                         Quiz, 'id', 'quiz_id'), name='post')
class CreateQuizTemplate(APIView):
    def post(self, request):
        # Receive data from serializer
        quiz = request.middleware_model_record

        quiz_template = QuizTemplate.objects.create(institute=quiz.institute, name=quiz.name,
                                                    single_question_at_a_time=quiz.single_question_at_a_time,
                                                    reanswerable=quiz.reanswerable, quiz_type=quiz.quiz_type,
                                                    shuffle_questions=quiz.shuffle_questions,
                                                    shuffle_choices=quiz.shuffle_choices,
                                                    apply_negative_scores=quiz.apply_negative_scores, public=quiz.public,
                                                    duration=quiz.duration, questions_count=quiz.questions_count,
                                                    description=quiz.description, _blueprint=quiz._blueprint, theme=quiz.theme,
                                                    price=quiz.price, finish_message=quiz.finish_message,  # TODO duplicate poster
                                                    poster=quiz.poster, poster_thumbnail=quiz.poster_thumbnail)

        quiz_template_data = QuizTemplateModelSerializer(quiz_template, context={"with_blueprint": True}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz_template': quiz_template_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(EditQuizTemplateInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_WRITE_EXAM__], QuizTemplate, 'pk', 'quiz_template_id'), name='post')
class EditQuizTemplate(APIView):

    def post(self, request):

        context_institute = request.middleware_institute
        quiz = request.middleware_model_record

        # Receive data from serializer
        name = request.middleware_serializer_data.get('name')
        rule_dict = request.middleware_serializer_data.get('rule')

        single_question_at_a_time = request.middleware_serializer_data.get('single_question_at_a_time')
        public = request.middleware_serializer_data.get('public')
        reanswerable = request.middleware_serializer_data.get('reanswerable')
        quiz_type = request.middleware_serializer_data.get('quiz_type')

        shuffle_questions = request.middleware_serializer_data.get('shuffle_questions')
        shuffle_choices = request.middleware_serializer_data.get('shuffle_choices')
        apply_negative_scores = request.middleware_serializer_data.get('apply_negative_scores')

        poster = request.middleware_serializer_data.get('poster')
        remove_poster = request.middleware_serializer_data.get('remove_poster')
        duration = request.middleware_serializer_data.get('duration')
        description = request.middleware_serializer_data.get('description')
        theme = request.middleware_serializer_data.get('theme')

        price = request.middleware_serializer_data.get('price')
        finish_message = request.middleware_serializer_data.get('finish_message')

        if rule_dict:
            rule = Rule.from_dict(rule_dict)
            # this will raise error if permission was not granted
            rule.permission_checker(context_institue=context_institute)

            blueprint = Blueprint.from_rule(rule=rule)
            quiz._blueprint = blueprint.to_dict()
            quiz.questions_count = blueprint.questions_count()

        quiz.name = name
        quiz.single_question_at_a_time = True if not reanswerable else single_question_at_a_time
        quiz.reanswerable = reanswerable
        quiz.public = public
        quiz.quiz_type = quiz_type
        quiz.shuffle_questions = shuffle_questions
        quiz.shuffle_choices = shuffle_choices
        quiz.apply_negative_scores = apply_negative_scores
        quiz.duration = duration

        quiz.description = description

        quiz.theme = theme
        quiz.price = price
        quiz.finish_message = finish_message
        if remove_poster and quiz.poster:
            quiz.poster = None
            quiz.poster_thumbnail = None
        elif poster:
            quiz.poster = compress_image(poster, max_side_size=conf.quiz_poster_compress_side_size)
            quiz.poster_thumbnail = compress_image(poster, max_side_size=conf.quiz_poster_thumb_compress_side_size)
        quiz.save()

        quiz_data = QuizTemplateModelSerializer(quiz, context={"with_blueprint": True}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz_template': quiz_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(serializer_decorator(ArchiveQuizTemplateInputSerializer), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_WRITE_EXAM_TEMPLATE__], QuizTemplate, 'pk', 'quiz_template_id'),
                  name='post')
class ArchiveQuizTemplate(APIView):

    def post(self, request):
        quiz_template = request.middleware_model_record
        quiz_template.archived = True
        quiz_template.save()

        quiz_data = QuizTemplateModelSerializer(quiz_template, context={"with_blueprint": False}).data
        return MyResponse(request, {'status': 'ok', 'message': 'Successful', 'data': {'quiz': quiz_data}},
                          status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(QuizTemplateMarketInputSerializer), name='post')
class QuizTemplateMarket(APIView):
    def post(self, request):
        # Receive data from serializer
        q = request.middleware_serializer_data.get('q')
        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        sort_by = sort_by_translator(request.middleware_serializer_data.get('sort_by'))
        reanswerable_filter = request.middleware_serializer_data.get('reanswerable_filter')
        shuffle_questions_filter = request.middleware_serializer_data.get('shuffle_questions_filter')
        shuffle_choices_filter = request.middleware_serializer_data.get('shuffle_choices_filter')
        single_question_at_a_time_filter = request.middleware_serializer_data.get('single_question_at_a_time_filter')
        archived_filter = request.middleware_serializer_data.get('archived_filter')
        ########################### Block 2 ################################
        quiz_queryset = QuizTemplate.objects.filter(featured=True)

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

        # reanswerable_filter
        if reanswerable_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(reanswerable=False)
        elif reanswerable_filter == 'only':
            quiz_queryset = quiz_queryset.filter(reanswerable=True)

        # shuffle_questions_filter
        if shuffle_questions_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(shuffle_questions=False)
        elif shuffle_questions_filter == 'only':
            quiz_queryset = quiz_queryset.filter(shuffle_questions=True)

        # shuffle_choices_filter
        if shuffle_choices_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(shuffle_choices=False)
        elif shuffle_choices_filter == 'only':
            quiz_queryset = quiz_queryset.filter(shuffle_choices=True)

        # single_question_at_a_time_filter
        if single_question_at_a_time_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(single_question_at_a_time=False)
        elif single_question_at_a_time_filter == 'only':
            quiz_queryset = quiz_queryset.filter(single_question_at_a_time=True)

        # archived_filter
        if archived_filter == 'exclude':
            quiz_queryset = quiz_queryset.filter(archived=False)
        elif archived_filter == 'only':
            quiz_queryset = quiz_queryset.filter(archived=True)
        # ########################## Block 3 ################################

        total_count = quiz_queryset.count()
        quiz_queryset = quiz_queryset.order_by(sort_by)[skip:skip + take]

        quiz_data = QuizTemplateModelSerializer(quiz_queryset, many=True).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'current_date_time': timezone.now(), 'total_count': total_count,
                                             'quiz_templates': quiz_data}}, status=status.HTTP_200_OK)


@method_decorator(serializer_decorator(QuizGateWayInputSerializer), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
class QuizGateWay(APIView):

    def post(self, request):

        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student
        entrance_type = request.middleware_entrance_type

        data = {}
        # TODO if quiz_sheet was available, use it instead
        data['info'] = QuizModelSerializer(quiz, context={"with_blueprint": False}).data
        data['institute'] = InstituteSerializerForExamAndInstituteAndQuestionBank(quiz.institute).data

        # history
        # for institute_student, there will be only one history available and it is the quiz_sheet with the entrance_token provided
        if institute_student:
            data['history'] = QuizSheetModelSerializerForHistory([quiz_sheet], many=True).data
            data['max_enter_count'] = 1
        else:
            sheets = QuizSheet.objects.filter(user=user, quiz=quiz)

            for sheet in sheets:
                error_status, message = sheet.can_be_continued_state()
                if error_status is not None and sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and sheet.state != QUIZ_SHEET_STATE_FINISHED:
                    quiz_sheet_finish_helper(sheet)

            # history data became too large for lambda to handle
            # LAMBDA_RUNTIME Failed to post handler success response. Http response code: 413.
            # Affirmed
            data['history'] = QuizSheetModelSerializerForHistory(sheets, many=True).data
            data['max_enter_count'] = 100

        # appending invoice to the gateway
        data['invoice'] = None
        if user:
            total_cost, institute_share, wikiazma_share, wage, tax = price_calculator_helper(user, institute_student, quiz)

            data['invoice'] = {"type": "quiz", "price": quiz.price, "wage": wage, "tax": tax, "total_cost": total_cost,
                               "meta_data": {'quiz_id': quiz.id}}

            data['balance'] = UserBalanceHistory.get_last_user_balance(user).remaining_balance

        data['current_date_time'] = timezone.now()

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


@method_decorator(serializer_decorator(QuizSheetReportInputSerializer), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
class QuizSheetReport(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student
        entrance_type = request.middleware_entrance_type

        if quiz_sheet is None:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz Sheet not found'}, status=status.HTTP_404_NOT_FOUND)

        error_status, message = quiz_sheet.can_be_continued_state()
        if error_status is not None and quiz_sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and quiz_sheet.state != QUIZ_SHEET_STATE_FINISHED:
            quiz_sheet_finish_helper(quiz_sheet)

        quiz_sheet_data = QuizSheetModelForReportCardSerializer(quiz_sheet).data

        data = {'quiz_sheet': quiz_sheet_data}

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


@method_decorator(serializer_decorator(QuizEnterInputSerializer), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
@method_decorator(quiz_sheet_status_validation(error_if_quiz_sheet_is_none=False, process_quiz_status=True), name='post')
@method_decorator(quiz_purchase_middleware(), name='post')
class QuizEnter(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student
        entrance_type = request.middleware_entrance_type

        should_pay = False
        if entrance_type == 'Public' and quiz_sheet is None:
            should_pay = True

        if not quiz_sheet:
            quiz_sheet = create_quiz_sheet_helper(quiz, user=user)

        if quiz_sheet.state == QUIZ_SHEET_STATE_NOT_ATTENDED:
            # TODO remove after new migration
            if quiz_sheet.stats is None:
                quiz_sheet.stats = []
            quiz_sheet.stats.append(quiz_sheet.state)
            quiz_sheet.state = QUIZ_SHEET_STATE_IN_PROGRESS
            quiz_sheet.entrance_date = timezone.now()
            quiz_sheet.save()

        if should_pay:
            quiz_purchase_handler(user, quiz, quiz_sheet)

        quiz_sheet_data = QuizSheetModelForEnterSerializer(quiz_sheet).data
        quiz_data = QuizModelSerializer(quiz).data

        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'quiz_sheet': quiz_sheet_data, 'quiz': quiz_data,
                                             'current_date_time': timezone.now()}}, status=status.HTTP_200_OK)


@method_decorator(serializer_decorator(SetAnswerInputSerializer), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
@method_decorator(quiz_sheet_status_validation(error_if_quiz_sheet_is_none=True), name='post')
class SetAnswer(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student

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

        answer_time = timezone.now()

        for new_user_input in user_inputs:
            new_user_input['answer_time'] = answer_time.isoformat()

            question = quiz_sheet.get_question_by_index(new_user_input['index'])

            if question is None:
                pass

            if question['question_type'] == QUESTION_TYPE_MULTIPLE_CHOICE:
                # selected_choices was optional in validators because in validators we do not know the question_type
                # here, we force it to be present
                if 'selected_choices' not in new_user_input:
                    raise serializers.ValidationError(
                        {'user_inputs': f'selected_choices is required for question with index={new_user_input["index"]}.'})
                # If selected_choices was present, it must be of type list
                # because the validators check the type of optional values if they are present
                # if type(new_user_input['selected_choices']) is not list:
                #     raise serializers.ValidationError(
                #         {'user_inputs': 'selected_choices must be a list'})

                user_input_chosen_indexes = []

                for chosen_index in new_user_input['selected_choices']:
                    # check for duplicate chosen_index
                    if chosen_index in user_input_chosen_indexes:
                        raise serializers.ValidationError({'user_inputs': "duplicate choice=" + str(
                            chosen_index) + ' in the list of selected_choices for question_index=' + str(question['index'])})
                    user_input_chosen_indexes.append(chosen_index)

                    if chosen_index not in question['choices'].keys():
                        raise serializers.ValidationError({'user_inputs': "answer_choice=" + str(
                            chosen_index) + ' was not found in the list of choices for question_index=' + str(
                            question['index']) + ". choices are " + str(question['choices'].keys())})

                    if len(user_input_chosen_indexes) > question['input_rules']['max_selectable_choices']:
                        raise serializers.ValidationError({
                            'user_inputs': f"max_selectable_choices is {question['input_rules']['max_selectable_choices']} but you have selected {len(user_input_chosen_indexes)} choices"})

            elif question['question_type'] == QUESTION_TYPE_DESCRIPTIVE:
                if 'answer_text' not in new_user_input:
                    raise serializers.ValidationError(
                        {'user_inputs': f'answer_text is required for question with index={new_user_input["index"]}'})

                if 'answer_text_format' not in new_user_input:
                    raise serializers.ValidationError(
                        {'user_inputs': f'answer_text_format is required for question with index={new_user_input["index"]}'})

                if type(new_user_input['answer_text']) is not str:
                    raise serializers.ValidationError({'user_inputs': 'answer_text  must be a string'})

                if type(new_user_input['answer_text_format']) is not str:
                    raise serializers.ValidationError({'user_inputs': 'answer_text_format  must be a string'})

                if len(new_user_input['answer_text']) > question['input_rules']['max_characters']:
                    raise serializers.ValidationError()

                if new_user_input['answer_text_format'] not in QUESTION_FORMAT_SERIALIZER_CHOICES:
                    raise serializers.ValidationError({
                        'question_text_format': f'question_text_format should be one of {", ".join(QUESTION_FORMAT_SERIALIZER_CHOICES)}'})

            else:
                raise serializers.ValidationError({'user_inputs': "set_answer is not implemented for your question type yet"})

            for old_user_input in quiz_sheet._quizee_inputs_sheet:
                if old_user_input['index'] == new_user_input['index']:
                    if not quiz_sheet.quiz.reanswerable:
                        return MyResponse(request,
                                          {'status': 'error', 'message': 'You cannot answer this question again.', 'data': {}},
                                          status=status.HTTP_400_BAD_REQUEST)
                    quiz_sheet._quizee_inputs_sheet.remove(old_user_input)
                    break

            quiz_sheet._quizee_inputs_sheet.append(new_user_input)

        quiz_sheet.save()

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


def quiz_sheet_finish_helper(quiz_sheet):
    corrections_list = auto_correct_helper(quiz_sheet)

    if quiz_sheet.stats is None:
        quiz_sheet.stats = []

    quiz_sheet.stats.append(quiz_sheet.state)
    quiz_sheet.state = QUIZ_SHEET_STATE_FINISHED
    # TODO finished_at=last_interaction
    quiz_sheet.finished_at = timezone.now()

    report_card, total_score, max_positive_score = generate_report_card_helper(quiz_sheet, corrections_list)

    if report_card is not None or quiz_sheet.quiz.quiz_type == QUIZ_TYPE_SURVEY:
        quiz_sheet.report_card = report_card
        try:
            quiz_sheet.final_score = round(total_score, 2)
        except:
            quiz_sheet.final_score = -1

        quiz_sheet.stats.append(quiz_sheet.state)
        quiz_sheet.state = QUIZ_SHEET_STATE_SCORES_PUBLISHED

    quiz_sheet.save()

    return max_positive_score


@method_decorator(serializer_decorator(FinishQuizSheetInputSerializer), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
@method_decorator(quiz_sheet_status_validation(error_if_quiz_sheet_is_none=True), name='post')
class FinishQuizSheet(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student

        max_positive_score = quiz_sheet_finish_helper(quiz_sheet)

        # TODO report_card should not be sent here
        # What if the report_card type was on_last_student?
        return MyResponse(request, {'status': 'ok', 'message': 'Successful',
                                    'data': {'final_score': quiz_sheet.final_score, 'report_card': quiz_sheet.report_card,
                                             'max_positive_score': max_positive_score,
                                             'finish_message': quiz_sheet.quiz.finish_message}}, status=status.HTTP_200_OK)


# TODO for now, user_token is required (Exams that are taken by RavanKa users cannot have invoice)
@method_decorator(serializer_decorator(InvoiceInputSerializer), name='post')
@method_decorator(authorizator_decorator(accept_user_token=True), name='post')
@method_decorator(entrance_validator_middleware(), name='post')
class Invoice(APIView):

    def post(self, request):
        quiz_sheet = request.middleware_quiz_sheet
        quiz = request.middleware_quiz
        user = request.middleware_user
        institute_student = request.middleware_institute_student

        total_cost, institute_share, wikiazma_share, wage, tax = price_calculator_helper(user, institute_student, quiz)

        context = {'status': 'ok', 'message': 'Successful', "data": {
            'invoice': {"type": "quiz", "price": quiz.price, "wage": wage, "tax": tax, "total_cost": total_cost,
                        "meta_data": {'quiz_id': quiz.id}},
            'balance': UserBalanceHistory.get_last_user_balance(user).remaining_balance}}
        return MyResponse(request, context, status=status.HTTP_200_OK)


@method_decorator(authorizator_decorator(accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(GenerateQuizSheetInputValidator), name='post')
class GenerateQuizSheet(APIView):
    def post(self, request):
        # Receive data from serializer
        wikiazma_institute_student_id = request.middleware_serializer_data.get('wikiazma_institute_student_id')
        referer_institute_student_identity = request.middleware_serializer_data.get('referer_institute_student_identity')
        wikiazma_quiz_id = request.middleware_serializer_data.get('wikiazma_quiz_id')
        allowed_start_at = request.middleware_serializer_data.get('allowed_start_at')
        allowed_finish_at = request.middleware_serializer_data.get('allowed_finish_at')
        duration = request.middleware_serializer_data.get('duration')

        if wikiazma_institute_student_id:
            try:
                institute_student = InstituteStudent.objects.get(institute=request.middleware_institute,
                                                                 id=wikiazma_institute_student_id)
            except:
                return MyResponse(request, {'status': 'error', 'message': 'Institute Student not found'},
                                  status=status.HTTP_404_NOT_FOUND)
        elif referer_institute_student_identity:
            try:
                institute_student = InstituteStudent.objects.get(institute=request.middleware_institute,
                                                                 referer_identity=referer_institute_student_identity)
            except:
                return MyResponse(request, {'status': 'error', 'message': 'Institute Student not found'},
                                  status=status.HTTP_404_NOT_FOUND)
        else:
            return MyResponse(request, {'status': 'error',
                                        'message': 'either wikiazma_institute_student_id or referer_institute_student_identity should be provided'},
                              status=status.HTTP_400_BAD_REQUEST)
        try:
            quiz = Quiz.objects.get(institute=request.middleware_institute, id=wikiazma_quiz_id)
        except:
            try:
                exam = Exam.objects.get(id=wikiazma_quiz_id)
                quiz = Quiz.objects.filter(name=exam.name)
            except:
                return MyResponse(request, {'status': 'error', 'message': 'Quiz not found'}, status=status.HTTP_404_NOT_FOUND)

        quiz_sheet = create_quiz_sheet_helper(quiz, institute_student=institute_student, allowed_start_at=allowed_start_at,
                                              allowed_finish_at=allowed_finish_at, duration=duration)

        quiz_sheet_data = QuizSheetForInstituteModelSerializer(quiz_sheet).data

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


@method_decorator(authorizator_decorator(accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(GetQuizIdFromExamIdInputValidator), name='post')
class GetQuizIdFromExamId(APIView):
    def post(self, request):
        wikiazma_exam_id = request.middleware_serializer_data.get('wikiazma_exam_id')
        logs_adder(wikiazma_exam_id)
        try:
            exam = Exam.objects.get(id=wikiazma_exam_id)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz not found'}, status=status.HTTP_404_NOT_FOUND)

        logs_adder(exam.name)
        quizzes = Quiz.objects.filter(name=exam.name, start=exam.start_date)
        if len(quizzes) == 0:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz not found'}, status=status.HTTP_404_NOT_FOUND)
        elif len(quizzes) > 1:
            return MyResponse(request, {'status': 'error', 'message': 'More Than one Quiz found'},
                              status=status.HTTP_404_NOT_FOUND)
        quiz = quizzes[0]

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


@method_decorator(authorizator_decorator(accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(VerifyQuizSheetInputValidator), name='post')
class InstituteStudentVerifyQuizSheet(APIView):
    def post(self, request):
        # Receive data from serializer
        quiz_sheet_id = request.middleware_serializer_data.get('quiz_sheet_id')

        try:
            quiz_sheet = QuizSheet.objects.get(quiz__institute=request.middleware_institute, referer_id=quiz_sheet_id)
        except:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz Sheet not found'}, status=status.HTTP_404_NOT_FOUND)

        error_status, message = quiz_sheet.can_be_continued_state()
        if error_status is not None and quiz_sheet.state != QUIZ_SHEET_STATE_SCORES_PUBLISHED and quiz_sheet.state != QUIZ_SHEET_STATE_FINISHED:
            quiz_sheet_finish_helper(quiz_sheet)

        quiz_sheet_data = GetQuizSheetForInstituteSerializer(quiz_sheet).data
        data = {'quiz_sheet': quiz_sheet_data}

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


@method_decorator(authorizator_decorator(accept_user_token=True, accept_server_api_key=True), name='post')
@method_decorator(serializer_decorator(QuizSheetCorrectionAddInputValidator), name='post')
@method_decorator(permission_decorator([dr.__PERMISSION_READ_EXAM__], QuizSheet, 'referer_id', 'quiz_sheet_id'), name='post')
class QuizSheetCorrectionAdd(APIView):
    def post(self, request):
        index = request.middleware_serializer_data.get('index')
        score = request.middleware_serializer_data.get('score')
        comment_for_student = request.middleware_serializer_data.get('comment_for_student')
        comment_for_other_correctors = request.middleware_serializer_data.get('comment_for_other_correctors')
        quiz_sheet = request.middleware_model_record
        caller = request.middleware_user
        # ########################## Block 2 ################################
        if quiz_sheet.state == QUIZ_SHEET_STATE_FINISHED or quiz_sheet.state == QUIZ_SHEET_STATE_SCORES_PUBLISHED:
            # ########################## Block 3 ################################
            corrections_query = QuestionScore.objects.filter(quiz_sheet=quiz_sheet, question_index=index)
            if corrections_query:
                corrections_query.update(score=score, comment_for_student=comment_for_student,
                                         comment_for_other_correctors=comment_for_other_correctors, corrector=caller)

            else:
                try:
                    selected_question_record = \
                        [question for question in quiz_sheet._solutions_sheet['solutions'] if question['index'] == index][0]
                    question = Question.objects.get(id=selected_question_record['question_id'])
                except:
                    return MyResponse(request, {'status': 'not_found', 'message': 'Question Not Found', },
                                      status=status.HTTP_404_NOT_FOUND)

                # ########################## Block 5 ################################

                blank = False
                try:
                    user_input_records = [item for item in quiz_sheet._quizee_inputs_sheet if item['index'] == index][0]
                    if user_input_records:
                        blank = True
                except:
                    pass
                # ########################## Block 6 ################################

                QuestionScore.objects.create(quiz_sheet=quiz_sheet, corrector=caller, question_index=index, score=score,
                                             question=question, blank=blank, comment_for_student=comment_for_student,
                                             comment_for_other_correctors=comment_for_other_correctors)

                # We should somehow indicate that there is unpublished corrections  # if quiz_sheet.state == QUIZ_SHEET_STATE_SCORES_PUBLISHED:  #     quiz_sheet.state = QUIZ_SHEET_STATE_FINISHED  #     quiz_sheet.save()

            # ########################## Block 7 ################################
            return MyResponse(request, {'status': 'ok', 'message': 'Successful'}, status=status.HTTP_200_OK)
        else:
            return MyResponse(request, {'status': 'error', 'message': 'Quiz not finished yet.', },
                              status=status.HTTP_403_FORBIDDEN)

# @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(PublishExamPageCorrectionInputSerializer), name='post')
# @method_decorator(check_access_permission_middleware([dr.__PERMISSION_WRITE_EXAM_PAGE__], ExamPage, 'id', 'exam_page_id'),
#                   name='post')
# class CorrectionPublish(APIView):
#     def post(self, request):
#         # TODO not tested
#         # ########################## Block 1 ################################
#         quiz_sheet = request.middleware_model_record
#
#         # Block 2 ################################ get all correction of this quiz_sheet
#         corrections_list = QuestionScore.objects.filter(quiz_sheet=quiz_sheet)
#
#         report_card, total_score, max_positive_score = generate_report_card_helper(
#             quiz_sheet, corrections_list)
#
#         # Block 3 ################################ update quiz_sheet
#
#         if report_card is not None and total_score is not None and max_positive_score is not None:
#
#             quiz_sheet.state_history.append(quiz_sheet.state)
#             quiz_sheet.state = QUIZ_SHEET_STATE_SCORES_PUBLISHED
#             quiz_sheet.report_card = report_card
#             quiz_sheet.final_score = round(total_score, 2)
#             quiz_sheet.save()
#             # ########################## Block 4 ################################
#
#             full_name = "-"
#             phone = None
#             email = None
#
#             if quiz_sheet.user:
#                 full_name = quiz_sheet.user.full_name
#                 phone = quiz_sheet.user.phone
#                 email = quiz_sheet.user.email
#
#             elif quiz_sheet.institute_student:
#                 full_name = quiz_sheet.institute_student.full_name
#                 phone = quiz_sheet.institute_student.phone
#                 email = quiz_sheet.institute_student.email
#
#             # ########################## Block 4 ################################
#
#             redirect_url = "https://panel.wikiazma.com/quiz/"
#             if quiz_sheet.quiz.public:
#                 redirect_url += f"p/{quiz_sheet.quiz.public_id}"
#             elif quiz_sheet.entrance_token:
#                 redirect_url += quiz_sheet.entrance_token
#             else:
#                 # TODO: HANDLE ASSIGNED QUIZZES
#                 pass
#
#             # ########################## Block 4 ################################
#
#             if phone:
#                 send_sms_correction(to=phone, examinee_name=full_name, exam_name=quiz_sheet.quiz.name,
#                                     institute_name=quiz_sheet.institute.name,
#                                     total_positive_score=total_score, max_positive_score=max_positive_score, report_card_url=redirect_url)
#
#             elif email:
#                 content = correction_email_content_builder(request, examinee_name=full_name, exam_name=quiz_sheet.quiz.name,
#                                                            institute_name=quiz_sheet.institute.name,
#                                                            score=str(total_score), max_positive_score=str(max_positive_score),
#                                                            report_card_url=redirect_url)
#
#                 send_html_email(content, email)
#
#             # ########################## Block 5 ################################
#
#             return MyResponse(request, {'status': 'ok', 'message': 'Successful',
#                                         'data': {'final_score': total_score, 'max_positive_score': max_positive_score}},
#                               status=status.HTTP_200_OK)
#         else:
#             return MyResponse(request, {'status': 'error', 'message': 'BadRequest', }, status=status.HTTP_400_BAD_REQUEST)
