import json
from datetime import datetime, timedelta

from django.utils import timezone

from exam.models import ExamPage, EXAM_PAGE_STATE_IN_PROGRESS, EXAM_PAGE_STATE_NOT_ATTENDED, Corrections, ENTRANCE_TYPE_PUBLIC, \
    ENTRANCE_TYPE_API_INVITE
# from exam.validators import QUERY_QUESTION_LIST_BY_FILTERING
QUERY_QUESTION_LIST_BY_FILTERING = 'TODO remove'


class ExamTestHelper:
    create_exam = "/exam/exam/create"
    edit_exam = "/exam/exam/edit"
    delete_exam = "/exam/exam/delete"
    list_exam = "/exam/exam/list"
    get_exam = "/exam/exam/get"
    get_exam_group = "/exam/exam/group/get"
    get_exam_stat = "/exam/exam/stat"
    create_exam_template = "/exam/template/create"
    list_exam_template = "/exam/template/list"
    get_exam_template = "/exam/template/get"
    get_exam_template_group = "/exam/template/group/get"
    list_public_exam_template = "/exam/public_template/list"
    get_public_exam_template = "/exam/public_template/get"
    generate_exam_page = "/exam/institute_student/page/generate"
    institute_student_exam_page_get = "/exam/institute_student/page/get"
    institute_student_exam_cover = "/exam/institute_student/page/cover"
    institute_student_exam_page_enter = "/exam/institute_student/page/enter"
    institute_student_exam_page_get_question = "/exam/institute_student/page/question/get"
    institute_student_exam_page_get_group = "/exam/institute_student/page/group/get"
    institute_student_exam_page_set_answer = "/exam/institute_student/page/set_answer"
    institute_student_exam_page_answer_file_add = "/exam/institute_student/page/answer_file/add"
    institute_student_exam_page_answer_file_delete = "/exam/institute_student/page/answer_file/delete"
    institute_student_exam_page_finish = "/exam/institute_student/page/finish"
    public_exam_cover = "/exam/public/page/cover"
    public_exam_enter = "/exam/public/page/enter"
    public_exam_page_get_group = "/exam/public/page/group/get"
    public_exam_page_get_question = "/exam/public/page/question/get"
    public_exam_page_set_answer = "/exam/public/page/set_answer"
    public_exam_page_finish = "/exam/public/page/finish"
    public_generate_invoice = "/exam/public/page/generate_invoice"
    public_exam_page_answer_file_add = "/exam/public/page/answer_file/add"
    public_exam_page_answer_file_delete = "/exam/public/page/answer_file/delete"
    pause_exam = "/exam/exam/pause"
    extend_exam = "/exam/exam/extend"
    taken_exam_page_list = '/exam/attended_exam_page/list'
    edit_exam_template = '/exam/template/edit'
    public_exam_get_answer_sheet = '/exam/public/page/answer_sheet/get'
    institute_student_exam_get_answer_sheet = '/exam/institute_student/page/answer_sheet/get'
    exam_page_get = '/exam/exam/page/get'
    exam_page_correction_add = '/exam/exam/page/correction/add'
    exam_page_correction_publish = '/exam/exam/page/correction/publish'
    date_time_format = '%Y-%m-%dT%H:%M:%S'

    query_question_by_id_institute_id = None
    query_question_by_id_object = []
    query_question_list_by_filtering_object = []

    def generate_query_question_by_id_list(self, institute_id, force_clear=False, public=False, token=None,
                                           question_type='multiple-choice'):

        if force_clear:
            self.query_question_by_id_object.clear()
        if len(self.query_question_by_id_object) == 0 or self.query_question_by_id_institute_id != institute_id:
            self.query_question_by_id_object.clear()
            question_bank_id = self.tester.questionbankHelper.create_question_bank(
                institute_id, public=public, token=token)
            for i in range(3):
                (response_data, status_code, error_message) = self.tester.questionHelper.create_question_with_defaults(
                    question_bank_id, token=token, question_type=question_type)
                question_id = response_data['data']['question']['id']
                self.query_question_by_id_object.append({
                    # Comment id #68246256; scores for 3 questions, the choices are in self.tester.questionHelper.create_question_with_defaults
                    "type": "QueryQuestionById",
                    "priority": i + 1,
                    "positive_score": 2.5,
                    "negative_score": 1.0,
                    "duration": 20.0,
                    "question_id": question_id,
                })

    def generate_query_question_list_by_filtering_list(self, institute_id, question_type='multiple-choice'):
        if len(self.query_question_list_by_filtering_object) == 0 or self.query_question_by_id_institute_id != institute_id:
            self.query_question_list_by_filtering_object.clear()
            question_bank_id = self.tester.questionbankHelper.create_question_bank(
                institute_id)

            # creating a question that should not be found in the generated_exam
            self.tester.questionHelper.create_question_with_defaults(question_bank_id,
                                                                     tags=json.dumps({'subject': 'physics'}), question_type=question_type)

            # creating questions that should be found in the generated_exam
            for i in range(5):
                self.tester.questionHelper.create_question_with_defaults(question_bank_id,
                                                                         tags=json.dumps({'subject': 'math'}), question_type=question_type)

            for i in range(1):
                self.query_question_list_by_filtering_object.append({
                    "type": QUERY_QUESTION_LIST_BY_FILTERING,
                    "priority": i + 1,
                    "count": 5,
                    "positive_score_per_question": 0.7,
                    "negative_score_per_question": 0.3,
                    "duration_per_question": 19.0,
                    "question_bank_id": question_bank_id,
                    "tag_filters": [{'key': 'subject', 'value': 'math'}],
                    # TODO test type_filter
                    "type_filter": 'descriptive'
                })

    def get_only_query_question_by_id_rule(self, institute_id):
        self.generate_query_question_by_id_list(institute_id)

        return {'groups': [
            {
                "id": 12345678,
                "name": "math1",
                "questions": self.query_question_by_id_object
            }
        ]}

    def get_general_rule(self, institute_id):
        self.generate_query_question_by_id_list(institute_id)
        self.generate_query_question_list_by_filtering_list(institute_id)

        return {'groups': [
            {
                "id": 12345678,
                "name": "math1",
                # Comment id #68246256; 3 questions are in query_question_by_id_object
                "questions": self.query_question_by_id_object + self.query_question_list_by_filtering_object
            }
        ]}

    def get_general_rule2(self, institute_id):
        self.generate_query_question_by_id_list(institute_id)
        self.generate_query_question_list_by_filtering_list(institute_id)

        return {'groups': [
            {
                "id": 12345678,
                "name": "math1",
                "questions": self.query_question_by_id_object
            },
            {
                "id": 12345679,
                "name": "physics1",
                "questions": self.query_question_list_by_filtering_object
            }
        ]}

    def get_genera_descriptive_rule(self, institute_id):
        self.generate_query_question_by_id_list(
            institute_id, question_type='descriptive')
        self.generate_query_question_list_by_filtering_list(
            institute_id, question_type='descriptive')

        return {'groups': [
            {
                "id": 12345678,
                "name": "math1",
                "questions": self.query_question_by_id_object}]}

    def rules_and_generated_exam_validator(self, rules, generated_exam, error_message):
        self.tester.assertIsNotNone(generated_exam, error_message)
        self.tester.assertEqual(len(rules['groups']), len(
            generated_exam['groups']), error_message)
        self.tester.assertIn(
            'questions_count', generated_exam['groups'][0], error_message)
        self.tester.assertIn(
            'durations', generated_exam['groups'][0], error_message)
        self.tester.assertIn(
            'positive_scores', generated_exam['groups'][0], error_message)
        self.tester.assertIn(
            'negative_scores', generated_exam['groups'][0], error_message)

    def general_rule_generated_exam_validator(self, rules, generated_exam, error_message):
        self.tester.assertEqual(
            155.0, generated_exam['groups'][0]['durations'], error_message)
        self.tester.assertEqual(
            11.0, generated_exam['groups'][0]['positive_scores'], error_message)
        self.tester.assertEqual(
            4.5, generated_exam['groups'][0]['negative_scores'], error_message)
        self.tester.assertEqual(
            8, len(generated_exam['groups'][0]['questions']), error_message)
        self.tester.assertEqual(
            8, generated_exam['groups'][0]['questions_count'], error_message)

    def __init__(self, tester):
        self.tester = tester

    name_index = 0

    def new_name(self):
        self.name_index += 1
        return 'new_name' + str(self.name_index)

    def exam_response_validator(self, response_data, error_message, context_institute_id, expected_questions_count, public, reanswer,
                                one_question_at_a_time, shuffle_questions, shuffle_answers, start_date, end_date, duration,
                                create_exam_template, override_institute_name, override_institute_logo, poster, exam_id=None,
                                delete_override_institute_logo=None, delete_poster=None, rules=None, delayable=False):

        old_exam = None
        if exam_id:
            (response_data2, status_code2, error_message2) = self.tester.post_with_token(self.get_exam, {
                'exam_id': exam_id
            }, application_json=True)
            self.tester.assertEqual(200, status_code2, error_message2)
            self.tester.assertEqual(
                'ok', response_data2['status'], error_message2)
            self.tester.assertEqual(
                'Successful', response_data2['message'], error_message2)
            old_exam = response_data2['data']['exam']

        self.tester.assertEqual('ok', response_data['status'], error_message)
        self.tester.assertEqual(
            'Successful', response_data['message'], error_message)

        self.tester.assertIn(
            'questions_count', response_data['data']['exam'], error_message)

        if expected_questions_count:
            self.tester.assertEqual(
                expected_questions_count, response_data['data']['exam']['questions_count'], error_message)

        if public is not None:
            if public:
                self.tester.assertEqual(
                    True, response_data['data']['exam']['public'], error_message)
                self.tester.assertIsNotNone(
                    response_data['data']['exam']['public_id'], error_message)

                if rules is not None:
                    # TODO we should not always generate the exam for public exams
                    self.tester.assertEqual(len(rules['groups']), len(response_data['data']['exam']['generated_exam']['groups']),
                                            error_message)
            else:
                self.tester.assertEqual(
                    False, response_data['data']['exam']['public'], error_message)
                self.tester.assertIsNone(
                    response_data['data']['exam']['public_id'], error_message)
        else:
            if exam_id:
                # old public value
                self.tester.assertEqual(
                    old_exam['public'], response_data['data']['exam']['public'], error_message)
            else:
                # default public behaviour
                pass
            pass

        if reanswer is not None:
            self.tester.assertEqual(
                reanswer, response_data['data']['exam']['reanswer'], error_message)

            if reanswer:
                self.tester.assertEqual(one_question_at_a_time, response_data['data']['exam']['one_question_at_a_time'],
                                        error_message)
            else:
                self.tester.assertEqual(
                    True, response_data['data']['exam']['one_question_at_a_time'], error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['reanswer'], response_data['data']['exam']['reanswer'], error_message)
            else:
                # default behaviour
                pass
            pass

        # if one_question_at_a_time is not None:
        # if reanswer:
        #     self.tester.assertEqual(one_question_at_a_time,
        #                             response_data['data']['exam']['one_question_at_a_time'], error_message)
        # else:
        #     self.tester.assertEqual(True, response_data['data']['exam']['one_question_at_a_time'],
        #                             error_message)
        # pass
        # else:
        # pass
        # if exam_id:
        #     # old value
        #     pass
        # else:
        #     # default behaviour
        #     pass
        # pass

        if shuffle_questions is not None:
            self.tester.assertEqual(shuffle_questions, response_data['data']['exam']['shuffle_questions'],
                                    error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['shuffle_questions'], response_data['data']['exam']['shuffle_questions'], error_message)
            else:
                # default behaviour
                pass
            pass

        if shuffle_answers is not None:
            self.tester.assertEqual(shuffle_answers, response_data['data']['exam']['shuffle_answers'],
                                    error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['shuffle_answers'], response_data['data']['exam']['shuffle_answers'], error_message)
            else:
                # default behaviour
                pass
            pass

        if start_date is not None:
            self.tester.assertIn(
                'start_date', response_data['data']['exam'], error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['start_date'], response_data['data']['exam']['start_date'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam']['start_date'], error_message)
            pass

        if end_date is not None:
            self.tester.assertIn(
                'end_date', response_data['data']['exam'], error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['end_date'], response_data['data']['exam']['end_date'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam']['end_date'], error_message)

        if duration is not None:
            self.tester.assertIn(
                'duration', response_data['data']['exam'], error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(
                    old_exam['duration'], response_data['data']['exam']['duration'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam']['duration'], error_message)

        if override_institute_name is not None:
            self.tester.assertEqual(
                override_institute_name, response_data['data']['exam']['override_institute_name'], error_message)
        else:
            if exam_id:
                # old value
                self.tester.assertEqual(old_exam['override_institute_name'], response_data['data']['exam']['override_institute_name'],
                                        error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam']['override_institute_name'], error_message)

        if delete_override_institute_logo is not None:
            if exam_id:
                self.tester.assertIsNone(
                    response_data['data']['exam']['override_institute_logo'], error_message)
        else:
            # old value
            if override_institute_logo is not None:
                # TODO
                # self.tester.assertEqual(override_institute_logo, response_data['data']['exam']['override_institute_logo'], error_message)
                pass
            else:
                if exam_id:
                    # old value
                    self.tester.assertEqual(old_exam['override_institute_logo'], response_data['data']['exam']['override_institute_logo'],
                                            error_message)
                else:
                    self.tester.assertIsNone(
                        response_data['data']['exam']['override_institute_logo'], error_message)

        if delete_poster is not None:
            if exam_id:
                self.tester.assertIsNone(
                    response_data['data']['exam']['poster'], error_message)
        else:
            # old value
            if poster is not None:
                # TODO
                # self.tester.assertEqual(override_institute_logo, response_data['data']['exam']['poster'], error_message)
                pass
            else:
                if exam_id:
                    # old value
                    self.tester.assertEqual(
                        old_exam['poster'], response_data['data']['exam']['poster'], error_message)
                else:
                    self.tester.assertIsNone(
                        response_data['data']['exam']['poster'], error_message)

        if create_exam_template is not None:
            # TODO
            pass

            # TODO these tests will never run for now
            if response_data['data']['exam']['generated_exam']:
                self.general_rule_generated_exam_validator(context_institute_id,
                                                           response_data['data']['exam']['generated_exam'],
                                                           error_message)

        if delayable is not None:
            self.tester.assertIn(
                'max_delay', response_data['data']['exam'], error_message)
            if delayable:

                self.tester.assertIsNotNone(
                    response_data['data']['exam']['max_delay'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam']['max_delay'], error_message)
        else:
            # old value
            self.tester.assertEqual(
                old_exam['delayable'], response_data['data']['exam']['delayable'], error_message)
            self.tester.assertEqual(
                old_exam['max_delay'], response_data['data']['exam']['max_delay'], error_message)

        # TODO price

    # This function returns different types of responses based on validate_success
    def create_exam_helper(self, context_institute_id, name=None, rules=None, expected_questions_count=None, public=None,
                           apply_negative_scores=None, reanswer=None, shuffle_questions=None, shuffle_answers=None,
                           one_question_at_a_time=None, create_exam_template=None, start_date=None, end_date=None, duration=None,
                           override_institute_name=None, override_institute_logo=None, poster=None, validate_success=True, delayable=False,
                           max_delay=None, price=None, finish_message=None, theme=None, description=None):

        if name is None:
            name = self.tester.examHelper.new_name()

        request = {
            'context_institute_id': context_institute_id,
            'name': name,
            'report_card_strategy': 'instantly',
            'theme': 'dark',
        }

        if validate_success and rules is not None and expected_questions_count is None:
            print(
                "when rules is not None, you better provide expected_questions_count too")
            self.tester.assertEqual(True, False)

        using_general_rule = False
        if rules is None:
            rules = self.get_general_rule(context_institute_id)
            using_general_rule = True

        request['rules'] = json.dumps(rules)

        if expected_questions_count is None:
            expected_questions_count = 8

        if public:
            request['public'] = public

        if apply_negative_scores:
            request['apply_negative_scores'] = apply_negative_scores

        if theme:
            request['theme'] = theme

        if reanswer:
            request['reanswer'] = reanswer

        if description:
            request['description'] = description

        if one_question_at_a_time:
            request['one_question_at_a_time'] = one_question_at_a_time

        if shuffle_questions:
            request['shuffle_questions'] = shuffle_questions

        if shuffle_answers:
            request['shuffle_answers'] = shuffle_answers

        if max_delay:
            request['max_delay'] = max_delay

        if create_exam_template:
            request['create_exam_template'] = create_exam_template

        if start_date:
            request['start_date'] = start_date

        if end_date:
            request['end_date'] = end_date

        if duration:
            request['duration'] = duration

        if delayable:
            request['delayable'] = delayable

        if finish_message:
            request['finish_message'] = finish_message

        if price:
            request['price'] = price

        if override_institute_name:
            request['override_institute_name'] = override_institute_name

        if override_institute_logo:
            request['override_institute_logo'] = override_institute_logo

        if poster:
            request['poster'] = poster

        (response_data, status_code, error_message) = self.tester.post_with_token(
            self.create_exam, request)

        if validate_success:
            self.exam_response_validator(response_data, error_message, context_institute_id, expected_questions_count, public, reanswer,
                                         one_question_at_a_time, shuffle_questions, shuffle_answers, start_date, end_date, duration,
                                         create_exam_template, override_institute_name, override_institute_logo, poster,
                                         delayable=delayable)

            return response_data['data']['exam']['id'], response_data['data']['exam']['public_id']
        else:
            return response_data, status_code, error_message

    def edit_exam_helper(self, institute_id, exam_id, name=None, theme=None, rules=None, expected_questions_count=None, public=False,
                         reanswer=True, shuffle_questions=None, shuffle_answers=None, one_question_at_a_time=False,
                         create_exam_template=None, start_date=None, end_date=None, duration=None, override_institute_name=None,
                         override_institute_logo=None, delete_override_institute_logo=None, poster=None, delete_poster=None,
                         validate_success=True, delayable=False, max_delay=None, price=None,
                         finish_message=None, description=None):

        request = {
            'exam_id': exam_id,
            'report_card_strategy': 'manual',
        }

        if rules is not None:
            request['rules'] = json.dumps(rules)

        if name is not None:
            request['name'] = name

        if theme is not None:
            request['theme'] = theme

        if description is not None:
            request['description'] = description
        if public is not None:
            request['public'] = public

        if reanswer is not None:
            request['reanswer'] = reanswer

        if delayable is not None:
            request['delayable'] = delayable

        if max_delay is not None:
            request['max_delay'] = max_delay

        if finish_message is not None:
            request['finish_message'] = finish_message

        if price is not None:
            request['price'] = price

        if one_question_at_a_time is not None:
            request['one_question_at_a_time'] = one_question_at_a_time

        if shuffle_questions is not None:
            request['shuffle_questions'] = shuffle_questions

        if shuffle_answers is not None:
            request['shuffle_answers'] = shuffle_answers

        if create_exam_template is not None:
            request['create_exam_template'] = create_exam_template

        if start_date is not None:
            request['start_date'] = start_date.strftime(self.date_time_format)

        if end_date is not None:
            request['end_date'] = end_date.strftime(self.date_time_format)

        if duration is not None:
            request['duration'] = duration

        if override_institute_name is not None:
            request['override_institute_name'] = override_institute_name

        if override_institute_logo is not None:
            request['override_institute_logo'] = override_institute_logo

        if delete_override_institute_logo is not None:
            request['delete_override_institute_logo'] = delete_override_institute_logo

        if poster is not None:
            request['poster'] = poster

        if delete_poster is not None:
            request['delete_poster'] = delete_poster

        (response_data, status_code, error_message) = self.tester.post_with_token(
            self.edit_exam, request)

        if validate_success:
            self.exam_response_validator(response_data, error_message, institute_id, expected_questions_count, public, reanswer,
                                         one_question_at_a_time, shuffle_questions, shuffle_answers, start_date, end_date, duration,
                                         create_exam_template, override_institute_name, override_institute_logo, poster,
                                         delete_override_institute_logo=delete_override_institute_logo, delete_poster=delete_poster,
                                         exam_id=exam_id, delayable=delayable)

            return response_data['data']['exam']['id']
        else:
            return response_data, status_code, error_message

    def delete_exam_helper(self, exam_id, validate_success=True):

        request = {
            'exam_id': exam_id,
        }

        (response_data, status_code, error_message) = self.tester.post_with_token(
            self.delete_exam, request)

        if validate_success:
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            return response_data, status_code, error_message
        else:
            return response_data, status_code, error_message

    def generate_exam_page_for_institute_student_helper(self, institute_id, start_date=None, end_date=None, duration=None,
                                                        one_question_at_a_time=False, reanswer=True, shuffle_questions=None,
                                                        shuffle_answers=None, rules=None, expected_questions_count=None, exam_name=None,
                                                        validate_success=True, public=None, apply_negative_scores=None, delayable=False):
        api_key = self.tester.instituteHelper.generate_api_key(institute_id)
        referer_institute_student_identity = "12345"
        wikiazma_institute_student_id = self.tester.instituteHelper.create_institute_student(api_key,
                                                                                             referer_institute_student_identity)
        exam_id, public_id = self.tester.examHelper.create_quiz_helper(institute_id,
                                                                       shuffle_questions=shuffle_questions,
                                                                       shuffle_answers=shuffle_answers,
                                                                       reanswer=reanswer,
                                                                       apply_negative_scores=apply_negative_scores,
                                                                       one_question_at_a_time=one_question_at_a_time,
                                                                       rules=rules, expected_questions_count=expected_questions_count,
                                                                       name=exam_name, public=public, delayable=delayable)

        (response_data, status_code, error_message) = self.tester.post_without_token(self.generate_exam_page, {
            'api_key': api_key,
            'wikiazma_institute_student_id': wikiazma_institute_student_id,
            'wikiazma_exam_id': exam_id,
            'start_date': '' if start_date is None else start_date,
            'end_date': '' if end_date is None else end_date,
            'duration': '' if duration is None else duration,
            'shuffle_questions': '' if shuffle_questions is None else shuffle_questions,
            'shuffle_answers': '' if shuffle_answers is None else shuffle_answers,
            'public': '' if public is None else public,
        })

        if validate_success:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            self.tester.assertEqual(EXAM_PAGE_STATE_NOT_ATTENDED, response_data['data']['exam_page']['state'],
                                    error_message)
            self.tester.assertIsNone(
                response_data['data']['exam_page']['final_score'], error_message)
            self.tester.assertIsNone(
                response_data['data']['exam_page']['entrance_date'], error_message)

            using_general_rule = False
            if rules is None:
                rules = self.get_general_rule(institute_id)
                using_general_rule = True

            # public exams have generated_exam in their exam object
            self.rules_and_generated_exam_validator(rules,
                                                    response_data['data']['exam_page']['generated_exam'],
                                                    error_message)
            if using_general_rule:
                self.general_rule_generated_exam_validator(rules,
                                                           response_data['data']['exam_page']['generated_exam'],
                                                           error_message)

            if start_date:
                self.tester.assertEqual(start_date, datetime.fromisoformat(
                    response_data['data']['exam_page']['start_date']), error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['start_date'], error_message)

            if end_date:
                self.tester.assertEqual(end_date, datetime.fromisoformat(
                    response_data['data']['exam_page']['end_date']), error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['end_date'], error_message)

            if duration:
                self.tester.assertEqual(
                    duration, response_data['data']['exam_page']['duration'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['duration'], error_message)

            return response_data['data']['exam_page']['id'], response_data['data']['exam_page']['entrance_token']
        else:
            return response_data, status_code, error_message

    def basic_enter_exam_helper(self, entrance_type, public_id=None, entrance_token=None):

        # print(entrance_token)
        # print(public_id)
        if entrance_type == ENTRANCE_TYPE_PUBLIC:
            (response_data, status_code, error_message) = self.tester.post_with_token(
                self.public_exam_enter, {'public_id': public_id}
            )
        elif entrance_type == ENTRANCE_TYPE_API_INVITE:
            (response_data, status_code, error_message) = self.tester.post_with_token(
                self.institute_student_exam_page_enter, {
                    'entrance_token': entrance_token}
            )
        else:
            return None, None, None

        return (response_data, status_code, error_message)

    def enter_exam_helper(self, context_institute_id, entrance_type, start_date=None, end_date=None, duration=None, reanswer=True,
                          shuffle_questions=None, shuffle_answers=None, one_question_at_a_time=False, price=None,
                          apply_negative_scores=None,
                          validate_success=True, rules=None, expected_questions_count=None, delayable=False, max_delay=None,
                          send_token=None, exam_name=None, finish_message=None):

        if entrance_type == ENTRANCE_TYPE_PUBLIC:
            exam_id, public_id = self.create_exam_helper(context_institute_id, public=True, start_date=start_date,
                                                         end_date=end_date,
                                                         duration=duration, reanswer=reanswer,
                                                         shuffle_questions=shuffle_questions,
                                                         shuffle_answers=shuffle_answers,
                                                         max_delay=max_delay,
                                                         one_question_at_a_time=one_question_at_a_time,
                                                         delayable=delayable,
                                                         rules=rules,
                                                         apply_negative_scores=apply_negative_scores,
                                                         expected_questions_count=expected_questions_count,
                                                         price=price,
                                                         finish_message=finish_message,
                                                         name=exam_name
                                                         )

            if send_token is False:
                (response_data, status_code, error_message) = self.tester.post_without_token(self.public_exam_enter, {
                    'public_id': public_id,
                })
            elif send_token is True or send_token is None:
                (response_data, status_code, error_message) = self.tester.post_with_token(self.public_exam_enter, {
                    'public_id': public_id,
                })

            try:
                exam_page_id = ExamPage.objects.get(exam_id=exam_id).id
            except:
                # if subscription_required is true and balance is not enough
                exam_page_id = None

        elif entrance_type == ENTRANCE_TYPE_API_INVITE:
            exam_page_id, entrance_token = self.generate_exam_page_for_institute_student_helper(context_institute_id,
                                                                                                start_date=start_date,
                                                                                                end_date=end_date,
                                                                                                duration=duration,
                                                                                                reanswer=reanswer,
                                                                                                one_question_at_a_time=one_question_at_a_time,
                                                                                                shuffle_questions=shuffle_questions,
                                                                                                shuffle_answers=shuffle_answers,
                                                                                                validate_success=True,
                                                                                                rules=rules,
                                                                                                apply_negative_scores=apply_negative_scores,
                                                                                                exam_name=exam_name,
                                                                                                expected_questions_count=expected_questions_count,
                                                                                                )
            # attending an exam in the start_date and end_date range
            (response_data, status_code, error_message) = self.tester.post_without_token(
                self.institute_student_exam_page_enter, {
                    'entrance_token': entrance_token,
                }
            )
        else:
            # TODO
            return None, None, None

        if validate_success:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['current_date_time'], error_message)
            self.tester.assertIn('first_available_question_index',
                                 response_data['data']['exam_page'], error_message)
            self.tester.assertIn('first_available_group_index',
                                 response_data['data']['exam_page'], error_message)
            self.tester.assertIn(
                'generated_exam', response_data['data']['exam_page'], error_message)
            self.tester.assertIn(
                'groups', response_data['data']['exam_page']['generated_exam'], error_message)
            self.tester.assertIn('questions', response_data['data']['exam_page']['generated_exam']['groups'][0],
                                 error_message)
            self.tester.assertNotIn('id', response_data['data']['exam_page']['generated_exam']['groups'][0],
                                    error_message)
            self.tester.assertNotIn('priority', response_data['data']['exam_page']['generated_exam']['groups'][0],
                                    error_message)

            # user_input_status will always be in generated_exam but the user_input itself depends on the reanswer field
            self.tester.assertIn('user_input_status',
                                 response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0],
                                 error_message)
            self.tester.assertEqual('not_submitted',
                                    response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0]['user_input_status'],
                                    error_message)
            if reanswer:
                self.tester.assertIn('user_input',
                                     response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0],
                                     error_message)
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0]['user_input'],
                    error_message)
            else:
                self.tester.assertNotIn('user_input',
                                        response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0],
                                        error_message)

            self.tester.assertNotIn(
                'final_score', response_data['data']['exam_page'], error_message)

            # if rules is None then generate_exam_page_for_institute_student_helper will use the general_rule
            if rules is None:
                self.tester.assertEqual(8, len(response_data['data']['exam_page']['generated_exam']['groups'][0]['questions']),
                                        error_message)
            if not reanswer:
                self.tester.assertEqual(0,
                                        response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][
                                            0]['index'],
                                        error_message)

                # for i in range(1, len(response_data['data']['exam_page']['generated_exam']['groups'])):
                #     # and the other groups must have zero questions
                #     self.tester.assertEqual(0, len(
                #         response_data['data']['exam_page']['generated_exam']['groups'][i]['questions']),
                #                             error_message)

                # TEST ID 84629056
                self.tester.assertNotIn('answer_rules',
                                        response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][
                                            0],
                                        error_message)
            else:
                # TEST ID 84629056
                self.tester.assertIn('answer_text',
                                     response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][0][
                                         'answer_rules'][
                                         'choices'][0], error_message)
                self.tester.assertNotIn('is_correct_choice',
                                        response_data['data']['exam_page']['generated_exam']['groups'][0]['questions'][
                                            0]['answer_rules'][
                                            'choices'][0],
                                        error_message)
            self.tester.assertNotIn(
                'corrections', response_data['data'], error_message)

            if start_date:
                self.tester.assertEqual(start_date, datetime.fromisoformat(
                    response_data['data']['exam_page']['start_date']), error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['start_date'], error_message)

            if end_date:
                self.tester.assertEqual(end_date, datetime.fromisoformat(
                    response_data['data']['exam_page']['end_date']), error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['end_date'], error_message)

            if duration:
                self.tester.assertEqual(
                    duration, response_data['data']['exam_page']['duration'], error_message)
            else:
                self.tester.assertIsNone(
                    response_data['data']['exam_page']['duration'], error_message)

            if entrance_type == ENTRANCE_TYPE_PUBLIC:
                device_token = response_data['data']['device_token']

                self.tester.assertEqual(EXAM_PAGE_STATE_IN_PROGRESS, ExamPage.objects.get(device_token=device_token).state,
                                        "expected the status to be IN_PROGRESS")

                return exam_page_id, public_id, device_token
            elif entrance_type == ENTRANCE_TYPE_API_INVITE:
                self.tester.assertEqual(EXAM_PAGE_STATE_IN_PROGRESS, ExamPage.objects.get(id=exam_page_id).state,
                                        "expected the status to be IN_PROGRESS")

                return exam_page_id, entrance_token, response_data['data']['device_token']
            else:
                # TODO
                return None, None, None
        else:
            return response_data, status_code, error_message

    def get_ready_an_exam_page_with_answers(self, institute_id, entrance_type, entrance_token=None, public_id=None, start_date=None,
                                            end_date=None, duration=None, reanswer=True, apply_negative_scores=None,
                                            shuffle_questions=None, shuffle_answers=None, one_question_at_a_time=False, exam_name=None,
                                            validate_success=True):
        if entrance_type == ENTRANCE_TYPE_API_INVITE:
            (exam_page_id, entrance_token, device_token) = self.enter_exam_helper(institute_id, entrance_type=ENTRANCE_TYPE_API_INVITE,
                                                                                  start_date=start_date,
                                                                                  end_date=end_date,
                                                                                  duration=duration,
                                                                                  reanswer=reanswer,
                                                                                  apply_negative_scores=apply_negative_scores,
                                                                                  shuffle_questions=shuffle_questions,
                                                                                  shuffle_answers=shuffle_answers,
                                                                                  one_question_at_a_time=one_question_at_a_time,
                                                                                  validate_success=True,
                                                                                  exam_name=exam_name)
        elif entrance_type == ENTRANCE_TYPE_PUBLIC:
            (exam_page_id, public_id, device_token) = self.enter_exam_helper(institute_id, entrance_type=ENTRANCE_TYPE_PUBLIC,
                                                                             start_date=start_date,
                                                                             end_date=end_date,
                                                                             duration=duration,
                                                                             reanswer=reanswer,
                                                                             apply_negative_scores=apply_negative_scores,
                                                                             shuffle_questions=shuffle_questions,
                                                                             shuffle_answers=shuffle_answers,
                                                                             one_question_at_a_time=one_question_at_a_time,
                                                                             exam_name=exam_name,
                                                                             validate_success=True)
        else:
            return None, None, None
        if reanswer:
            # set all three answers together
            if entrance_type == ENTRANCE_TYPE_API_INVITE:
                (response_data, status_code, error_message) = self.tester.post_without_token(
                    self.institute_student_exam_page_set_answer, {
                        'device_token': device_token,
                        'entrance_token': entrance_token,
                        'go_to_next_question': True,
                        'user_inputs': json.dumps(
                            # Comment id #68246256; the answer to 3 first questions
                            [{'group_index': 0, 'question_index': 0, 'answer_indexes': [0, 1]},
                             {'group_index': 0, 'question_index': 1, 'answer_indexes': [0, 1]},
                             {'group_index': 0, 'question_index': 2, 'answer_indexes': [0, 1]}])
                    })
            elif entrance_type == ENTRANCE_TYPE_PUBLIC:
                (response_data, status_code, error_message) = self.tester.post_without_token(
                    self.public_exam_page_set_answer, {
                        'device_token': device_token,
                        'public_id': public_id,
                        'go_to_next_question': True,
                        'user_inputs': json.dumps(
                            [{'group_index': 0, 'question_index': 0, 'answer_indexes': [0, 1]},
                             {'group_index': 0, 'question_index': 1, 'answer_indexes': [0, 1]},
                             {'group_index': 0, 'question_index': 2, 'answer_indexes': [0, 1]}])
                    })
            if validate_success:
                self.tester.assertEqual(200, status_code, error_message)
                self.tester.assertEqual(
                    'ok', response_data['status'], error_message)
                self.tester.assertEqual(
                    'Successful', response_data['message'], error_message)

                self.tester.assertEqual(
                    3, len(ExamPage.objects.get(id=exam_page_id).user_inputs))
                self.tester.assertEqual(
                    1, len(ExamPage.objects.get(id=exam_page_id).user_inputs_history))
                if entrance_type == ENTRANCE_TYPE_API_INVITE:
                    return exam_page_id, entrance_token, device_token
                elif entrance_type == ENTRANCE_TYPE_PUBLIC:
                    return exam_page_id, public_id, device_token
                else:
                    return None, None, None
            else:
                return response_data, status_code, error_message
        else:
            response_data = None
            status_code = None
            error_message = None
            for i in range(0, 3):
                if entrance_type == ENTRANCE_TYPE_API_INVITE:
                    (response_data, status_code, error_message) = self.tester.post_without_token(
                        self.institute_student_exam_page_set_answer, {
                            'device_token': device_token,
                            'entrance_token': entrance_token,
                            'go_to_next_question': True,
                            'user_inputs': json.dumps([{'group_index': 0, 'question_index': i, 'answer_indexes': [1, 2]}])
                        })
                elif entrance_type == ENTRANCE_TYPE_PUBLIC:
                    (response_data, status_code, error_message) = self.tester.post_without_token(
                        self.public_exam_page_set_answer, {
                            'device_token': device_token,
                            'public_id': public_id,
                            'go_to_next_question': True,
                            'user_inputs': json.dumps([{'group_index': 0, 'question_index': i, 'answer_indexes': [1, 2]}])
                        })

                if validate_success:
                    self.tester.assertEqual(200, status_code, error_message)
                    self.tester.assertEqual(
                        'ok', response_data['status'], error_message)
                    self.tester.assertEqual(
                        'Successful', response_data['message'], error_message)

                    self.tester.assertEqual(
                        i + 1, len(ExamPage.objects.get(id=exam_page_id).user_inputs))
                    self.tester.assertEqual(
                        i + 1, len(ExamPage.objects.get(id=exam_page_id).user_inputs_history))

            if validate_success:
                if entrance_type == ENTRANCE_TYPE_API_INVITE:
                    return exam_page_id, entrance_token, device_token
                elif entrance_type == ENTRANCE_TYPE_PUBLIC:
                    return exam_page_id, public_id, device_token
                else:
                    return None, None, None
            else:
                return response_data, status_code, error_message

    def finish_an_exam_page_with_answers(self, institute_id, entrance_type, start_date=None, end_date=None, duration=None, reanswer=True,
                                         shuffle_questions=None, shuffle_answers=None, one_question_at_a_time=False, exam_name=None,
                                         apply_negative_scores=None, validate_success=True):
        if entrance_type == ENTRANCE_TYPE_API_INVITE:
            (exam_page_id, entrance_token, device_token) = self.get_ready_an_exam_page_with_answers(institute_id,
                                                                                                    entrance_type=ENTRANCE_TYPE_API_INVITE,
                                                                                                    start_date=start_date,
                                                                                                    end_date=end_date,
                                                                                                    duration=duration,
                                                                                                    reanswer=reanswer,
                                                                                                    apply_negative_scores=apply_negative_scores,
                                                                                                    shuffle_questions=shuffle_questions,
                                                                                                    shuffle_answers=shuffle_answers,
                                                                                                    one_question_at_a_time=one_question_at_a_time,
                                                                                                    exam_name=exam_name,
                                                                                                    validate_success=True)

            (response_data, status_code, error_message) = self.tester.post_without_token(
                self.institute_student_exam_page_finish, {
                    'device_token': device_token,
                    'entrance_token': entrance_token,
                })
        elif entrance_type == ENTRANCE_TYPE_PUBLIC:
            (exam_page_id, public_id, device_token) = self.get_ready_an_exam_page_with_answers(institute_id,
                                                                                               entrance_type=ENTRANCE_TYPE_PUBLIC,
                                                                                               start_date=start_date,
                                                                                               end_date=end_date,
                                                                                               duration=duration,
                                                                                               reanswer=reanswer,
                                                                                               apply_negative_scores=apply_negative_scores,
                                                                                               shuffle_questions=shuffle_questions,
                                                                                               shuffle_answers=shuffle_answers,
                                                                                               one_question_at_a_time=one_question_at_a_time,
                                                                                               exam_name=exam_name,
                                                                                               validate_success=True)

            (response_data, status_code, error_message) = self.tester.post_without_token(
                self.public_exam_page_finish, {
                    'device_token': device_token,
                    'public_id': public_id,
                })
        else:

            return None, None, None

        if validate_success:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)

            self.tester.assertIn(
                'final_score', response_data['data'], error_message)
            self.tester.assertIn('max_positive_score',
                                 response_data['data'], error_message)
            self.tester.assertEqual(
                11.0, response_data['data']['max_positive_score'], error_message)

            if not shuffle_answers and not shuffle_questions:
                self.tester.assertNotEqual(0, Corrections.objects.filter(exam_page_id=exam_page_id).count(),
                                           error_message)

                # For manually calculating scores, checkout Comment id #68246256
                if apply_negative_scores:
                    self.tester.assertEqual(
                        0.75, response_data['data']['final_score'], error_message)
                else:
                    self.tester.assertEqual(
                        3.75, response_data['data']['final_score'], error_message)

            if shuffle_answers:
                self.tester.assertNotEqual(0, Corrections.objects.filter(exam_page_id=exam_page_id).count(),
                                           error_message)
                self.tester.assertGreaterEqual(
                    7.5, response_data['data']['final_score'], error_message)

            if shuffle_questions:
                self.tester.assertNotEqual(0, Corrections.objects.filter(exam_page_id=exam_page_id).count(),
                                           error_message)
                self.tester.assertGreaterEqual(
                    7.5, response_data['data']['final_score'], error_message)

            if entrance_type == ENTRANCE_TYPE_API_INVITE:
                return exam_page_id, entrance_token, device_token
            elif entrance_type == ENTRANCE_TYPE_PUBLIC:
                return exam_page_id, public_id, device_token
            else:
                return None, None, None

        else:
            return response_data, status_code, error_message

    def get_question_helper(self, device_token, group_index, question_index, entrance_token=None, public_id=None,
                            should_return_index_error=False, should_return_not_found_error=False, first_available_question_index=0,
                            first_available_group_index=0, expected_answer_time=None):
        self.tester.assertEqual(False, should_return_not_found_error * should_return_index_error,
                                "both should_return_not_found_error and should_return_index_error cannot be true")

        request = {'device_token': device_token,
                   'group_index': group_index, 'question_index': question_index, }
        if entrance_token is not None:
            request['entrance_token'] = entrance_token
        else:
            request['public_id'] = public_id

        (response_data, status_code, error_message) = self.tester.post_without_token(
            self.institute_student_exam_page_get_question if entrance_token is not None else self.public_exam_page_get_question, request)

        if should_return_index_error:
            self.tester.assertEqual(409, status_code, error_message)
            self.tester.assertEqual(
                'index_error', response_data['status'], error_message)
            self.tester.assertEqual(first_available_question_index,
                                    response_data['data']['first_available_question_index'], error_message)
            self.tester.assertEqual(
                first_available_group_index, response_data['data']['first_available_group_index'], error_message)
        elif should_return_not_found_error:
            self.tester.assertEqual(404, status_code, error_message)
            self.tester.assertEqual(
                'not_found', response_data['status'], error_message)
        else:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            self.tester.assertIn(
                'question', response_data['data'], error_message)
            self.tester.assertNotIn(
                'question_id', response_data['data']['question'], error_message)
            self.tester.assertNotIn(
                'type', response_data['data']['question'], error_message)
            self.tester.assertNotIn(
                'priority', response_data['data']['question'], error_message)
            self.tester.assertIn(
                'user_input', response_data['data']['question'], error_message)
            self.tester.assertIn(
                'user_input_status', response_data['data']['question'], error_message)
            self.tester.assertIn(
                'index', response_data['data']['question'], error_message)
            self.tester.assertEqual(
                question_index, response_data['data']['question']['index'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['duration'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['answer_rules'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['question_text'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['question_type'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['negative_score'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['positive_score'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['question_text_format'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['question']['user_input_status'], error_message)

            question_type = response_data['data']['question']['question_type']
            if question_type == 'multiple-choice':
                self.tester.assertLess(1, len(
                    response_data['data']['question']['answer_rules']['choices']), error_message)
                self.tester.assertIsNotNone(
                    response_data['data']['question']['answer_rules']['choices'][0]['index'], error_message)
                self.tester.assertIsNotNone(
                    response_data['data']['question']['answer_rules']['choices'][0]['answer_text'], error_message)
                self.tester.assertIsNotNone(response_data['data']['question']['answer_rules']['choices'][0]['answer_text_format'],
                                            error_message)
                self.tester.assertNotIn(
                    'id', response_data['data']['question']['answer_rules']['choices'][0], error_message)
                self.tester.assertNotIn(
                    'is_correct_choice', response_data['data']['question']['answer_rules']['choices'][0], error_message)
                self.tester.assertNotIn(
                    'priority', response_data['data']['question']['answer_rules']['choices'][0], error_message)
            else:
                # TODO
                pass

            if response_data['data']['question']['user_input'] is not None:
                self.tester.assertIsNotNone(
                    response_data['data']['question']['user_input']['answer_time'], error_message)
                self.tester.assertIsNotNone(
                    response_data['data']['question']['user_input']['question_index'], error_message)
                self.tester.assertIsNotNone(
                    response_data['data']['question']['user_input']['group_index'], error_message)
                # TODO ['answer_text', 'answer_text_format', 'answer_indexes']
                if expected_answer_time:
                    self.tester.assertGreaterEqual(expected_answer_time + timedelta(seconds=10),
                                                   datetime.fromisoformat(
                                                       response_data['data']['question']['user_input']['answer_time']),
                                                   error_message)
                    self.tester.assertLess(expected_answer_time - timedelta(seconds=5),
                                           datetime.fromisoformat(
                                               response_data['data']['question']['user_input']['answer_time']),
                                           error_message)

    def get_group_helper(self, device_token, group_index, entrance_token=None, public_id=None, should_return_index_error=False,
                         should_return_not_found_error=False, first_available_question_index=0, first_available_group_index=0,
                         error_400=False, message='You should send only one answer at a time', status='error'):
        self.tester.assertEqual(False, should_return_not_found_error * should_return_index_error,
                                "both should_return_not_found_error and should_return_index_error cannot be true")

        request = {'device_token': device_token, 'group_index': group_index}
        if entrance_token is not None:
            request['entrance_token'] = entrance_token
        else:
            request['public_id'] = public_id

        (response_data, status_code, error_message) = self.tester.post_without_token(
            self.institute_student_exam_page_get_group if entrance_token is not None else self.public_exam_page_get_group, request)

        if should_return_index_error:
            self.tester.assertEqual(409, status_code, error_message)

            self.tester.assertEqual(
                'index_error', response_data['status'], error_message)
            self.tester.assertEqual(first_available_question_index,
                                    response_data['data']['first_available_question_index'], error_message)
            self.tester.assertEqual(
                first_available_group_index, response_data['data']['first_available_group_index'], error_message)
        elif should_return_not_found_error:
            self.tester.assertEqual(404, status_code, error_message)
            self.tester.assertEqual(
                'not_found', response_data['status'], error_message)
        elif error_400:
            self.tester.assertEqual(400, status_code, error_message)
            self.tester.assertEqual(
                status, response_data['status'], error_message)
            self.tester.assertEqual(
                message, response_data['message'], error_message)

        else:

            self.tester.assertEqual(200, status_code, error_message)

            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)

    def exam_page_set_answer_helper(self, device_token, user_inputs, entrance_token=None, public_id=None, go_to_next_question=False,
                                    should_return_index_error=False, should_return_not_found_error=False, error_400=False,
                                    first_available_question_index=0, first_available_group_index=0, error_401=False,
                                    message='You should send only one answer at a time', status='error'):
        request = {'device_token': device_token, 'user_inputs': user_inputs,
                   'go_to_next_question': go_to_next_question}
        if entrance_token is not None:
            request['entrance_token'] = entrance_token
        else:
            request['public_id'] = public_id

        (response_data, status_code, error_message) = self.tester.post_without_token(
            self.institute_student_exam_page_set_answer if entrance_token is not None else self.public_exam_page_set_answer, request)

        if should_return_index_error:
            self.tester.assertEqual(409, status_code, error_message)

            self.tester.assertEqual(
                'index_error', response_data['status'], error_message)
            self.tester.assertEqual(first_available_question_index,
                                    response_data['data']['first_available_question_index'], error_message)
            self.tester.assertEqual(
                first_available_group_index, response_data['data']['first_available_group_index'], error_message)
        elif should_return_not_found_error:
            self.tester.assertEqual(404, status_code, error_message)
            self.tester.assertEqual(
                'not_found', response_data['status'], error_message)

        elif error_401:
            self.tester.assertEqual(401, status_code, error_message)
            self.tester.assertEqual(
                "error", response_data['status'], error_message)
            self.tester.assertEqual(
                'Access denied', response_data['message'], error_message)

        elif error_400:
            self.tester.assertEqual(400, status_code, error_message)
            self.tester.assertEqual(
                status, response_data['status'], error_message)
            self.tester.assertEqual(
                message, response_data['message'], error_message)
        else:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)

            self.get_question_helper(device_token, first_available_question_index, first_available_group_index,
                                     entrance_token=entrance_token, public_id=public_id,
                                     expected_answer_time=timezone.now())

        return response_data, status_code, error_message

    def get_exam_cover_helper(self, entrance_token=None, public_id=None, device_token=None, expected_cover_keys=None):
        request = {}
        if device_token is not None:
            request['device_token'] = device_token
        if entrance_token is not None:
            request['entrance_token'] = entrance_token
            # TODO should receive send_token in the function
            (response_data, status_code, error_message) = self.tester.post_with_token(
                self.institute_student_exam_cover, request)
        else:
            request['public_id'] = public_id
            (response_data, status_code, error_message) = self.tester.post_with_token(
                self.public_exam_cover, request)
        self.tester.assertEqual(200, status_code, error_message)
        self.tester.assertEqual('ok', response_data['status'], error_message)
        self.tester.assertIn('current_date_time',
                             response_data['data'], error_message)

        self.tester.assertIn('institute', response_data['data'], error_message)

        self.tester.assertIn('poster', response_data['data'], error_message)
        self.tester.assertIn('poster_thumbnail',
                             response_data['data'], error_message)
        self.tester.assertIn('name', response_data['data'], error_message)
        self.tester.assertIn(
            'description', response_data['data'], error_message)
        self.tester.assertIn('report_card_strategy',
                             response_data['data'], error_message)
        self.tester.assertIn('report_card_date',
                             response_data['data'], error_message)
        self.tester.assertIn(
            'entrance_date', response_data['data'], error_message)
        self.tester.assertIn('price', response_data['data'], error_message)
        if device_token is not None:
            self.tester.assertIsNotNone(
                response_data['data']['entrance_date'], error_message)
        else:
            self.tester.assertIsNone(
                response_data['data']['entrance_date'], error_message)
        self.tester.assertIn(
            'start_date', response_data['data'], error_message)
        self.tester.assertIn('end_date', response_data['data'], error_message)
        self.tester.assertIn('duration', response_data['data'], error_message)
        self.tester.assertIn('state', response_data['data'], error_message)
        self.tester.assertNotIn(
            'final_score', response_data['data'], error_message)
        self.tester.assertNotIn(
            'generated_exam', response_data['data'], error_message)
        self.tester.assertNotIn(
            'user_inputs', response_data['data'], error_message)
        self.tester.assertNotIn(
            'corrections', response_data['data'], error_message)
        if public_id is not None:
            if device_token is not None:
                self.tester.assertEqual(
                    'in_progress', response_data['data']['state'], error_message)
            else:
                self.tester.assertEqual(
                    'not_generated', response_data['data']['state'], error_message)
        else:
            if device_token is not None:
                self.tester.assertEqual(
                    'in_progress', response_data['data']['state'], error_message)
            else:
                self.tester.assertEqual(
                    'not_attended', response_data['data']['state'], error_message)

        new_cover_keys = response_data['data'].keys()

        if expected_cover_keys is not None:
            self.tester.assertEqual(len(new_cover_keys), len(
                expected_cover_keys), str(new_cover_keys) + str(expected_cover_keys))

            for expected_cover_key in expected_cover_keys:
                self.tester.assertIn(expected_cover_key, new_cover_keys,
                                     expected_cover_key + " was not found in " + str(new_cover_keys))

        return new_cover_keys

    def generate_invoice_helper(self, public_id=None, device_token=None, expected_total_cost=None):
        request = {}
        if device_token is not None:
            request['device_token'] = device_token
        if public_id:
            request['public_id'] = public_id
            (response_data, status_code, error_message) = self.tester.post_with_token(
                self.public_generate_invoice, request)
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            self.tester.assertIn(
                'invoice', response_data['data'], error_message)
            self.tester.assertIn(
                'balance', response_data['data'], error_message)
            self.tester.assertIsNotNone(
                response_data['data']['balance'], error_message)
            self.tester.assertLessEqual(
                0, response_data['data']['balance'], error_message)

            if expected_total_cost:
                self.tester.assertEqual(
                    expected_total_cost, response_data['data']['invoice']['total_cost'], error_message)

    def answer_file_helper(self, device_token, group_index, question_index, file, public_id=None, entrance_token=None, error_400=False,
                           error_409=False):
        request = {'device_token': device_token, 'group_index': group_index,
                   'question_index': question_index, 'file': file}
        if entrance_token is not None:
            request['entrance_token'] = entrance_token
        else:
            request['public_id'] = public_id

        (response_data, status_code, error_message) = self.tester.post_with_token(
            self.institute_student_exam_page_answer_file_add if entrance_token is not None else self.public_exam_page_answer_file_add,
            request)

        if error_400:
            self.tester.assertEqual(
                'error', response_data['status'], error_message)
            self.tester.assertIn(
                'you can not attach file to this question', response_data['message'], error_message)
        elif error_409:
            self.tester.assertEqual(409, status_code, error_message)
            self.tester.assertEqual(
                'index_error', response_data['status'], error_message)
        else:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
        return response_data, status_code, error_message

    def delete_answer_file_helper(self, file_id, device_token, group_index, question_index, public_id=None, entrance_token=None,
                                  error_404=False):
        request = {'file_id': file_id, 'device_token': device_token,
                   'group_index': group_index, 'question_index': question_index}

        if entrance_token is not None:
            request['entrance_token'] = entrance_token
        else:
            request['public_id'] = public_id

        (response_data, status_code, error_message) = self.tester.post_without_token(
            self.institute_student_exam_page_answer_file_delete if entrance_token is not None else self.public_exam_page_answer_file_delete,
            request)
        if error_404:
            self.tester.assertEqual(404, status_code, error_message)
            self.tester.assertEqual(
                'not_found', response_data['status'], error_message)
            self.tester.assertEqual(
                'File not found', response_data['message'], error_message)
        else:
            self.tester.assertEqual(200, status_code, error_message)
            self.tester.assertEqual(
                'ok', response_data['status'], error_message)
            self.tester.assertEqual(
                'Successful', response_data['message'], error_message)
            return response_data, status_code, error_message

    def pause_exam_helper(self, exam_id, pause):
        request = {'exam_id': exam_id, 'pause': pause}

        if pause is not None:
            request['pause'] = pause

        (response_data, status_code, error_message) = self.tester.post_with_token(self.pause_exam,
                                                                                  request)

        self.tester.assertEqual(200, status_code, error_message)
        self.tester.assertEqual('ok', response_data['status'], error_message)
        self.tester.assertEqual(
            'Successful', response_data['message'], error_message)

    def extend_exam_page_helper(self, exam_id, start_date, end_date, duration):
        request = {'exam_id': exam_id}

        if start_date is not None:
            request['start_date'] = start_date

        if end_date is not None:
            request['end_date'] = end_date

        if duration is not None:
            request['duration'] = duration

        (response_data, status_code, error_message) = self.tester.post_with_token(
            self.extend_exam, request)

        return response_data, status_code, error_message

    def taken_exam_page_list_helper(self, skip, take):
        request = {'skip': skip, 'take': take}

        (response_data, status_code, error_message) = self.tester.post_with_token(self.taken_exam_page_list,
                                                                                  request)

        return response_data, status_code, error_message

    def edit_exam_template_helper(self, exam_template_id, name=None, rules=None, one_question_at_a_time=None, reanswer=None,
                                  report_card_strategy=None, shuffle_questions=None, shuffle_answers=None, apply_negative_scores=None,
                                  examiner_info_visibility=None, override_institute_name=None, override_institute_logo=None, theme=None,
                                  description=None, duration=None, delayable=None, max_delay=None, price=None, finish_message=None,
                                  poster=None, delete_poster=False):
        request = {'exam_template_id': exam_template_id}

        if name is not None:
            request['name'] = name

        if rules is not None:
            request['rules'] = rules

        if poster is not None:
            request['poster'] = poster

        if one_question_at_a_time is not None:
            request['one_question_at_a_time'] = one_question_at_a_time

        if delete_poster is not None:
            request['delete_poster'] = delete_poster

        if reanswer is not None:
            request['reanswer'] = reanswer

        if report_card_strategy is not None:
            request['report_card_strategy'] = report_card_strategy

        if shuffle_questions is not None:
            request['shuffle_questions'] = shuffle_questions

        if shuffle_answers is not None:
            request['shuffle_answers'] = shuffle_answers

        if apply_negative_scores is not None:
            request['apply_negative_scores'] = apply_negative_scores

        if theme is not None:
            request['theme'] = theme

        if description is not None:
            request['description'] = description

        if duration is not None:
            request['duration'] = duration

        if delayable is not None:
            request['delayable'] = delayable

        if max_delay is not None:
            request['max_delay'] = max_delay

        if price is not None:
            request['price'] = price

        if finish_message is not None:
            request['finish_message'] = finish_message

        if override_institute_logo is not None:
            request['override_institute_logo'] = override_institute_logo

        if override_institute_name is not None:
            request['override_institute_name'] = override_institute_name

        if examiner_info_visibility is not None:
            request['examiner_info_visibility'] = examiner_info_visibility

        (response_data, status_code, error_message) = self.tester.post_with_token(self.edit_exam_template,
                                                                                  request)

        return response_data, status_code, error_message

    def get_exam_page_helper(self, exam_page_id):
        request = {'exam_page_id': exam_page_id}

        (response_data, status_code, error_message) = self.tester.post_with_token(self.exam_page_get,
                                                                                  request)

        return response_data, status_code, error_message

    def exam_page_correction_add_helper(self, exam_page_id, group_index, question_index, score, comment_for_student=None,
                                        comment_for_other_correctors=None):
        request = {'exam_page_id': exam_page_id, 'group_index': group_index,
                   'question_index': question_index, 'score': score}

        if comment_for_other_correctors:
            request['comment_for_other_correctors'] = comment_for_other_correctors

        if comment_for_student:
            request['comment_for_student'] = comment_for_student

        (response_data, status_code, error_message) = self.tester.post_with_token(self.exam_page_correction_add,
                                                                                  request)

        return response_data, status_code, error_message

    def exam_page_correction_publish_helper(self, exam_page_id):
        request = {'exam_page_id': exam_page_id}

        (response_data, status_code, error_message) = self.tester.post_with_token(self.exam_page_correction_publish,
                                                                                  request)

        return response_data, status_code, error_message

    def institute_student_finish_exam_page_helper(self, institute_id, start_date=None, end_date=None, duration=None, reanswer=True,
                                                  shuffle_questions=None, shuffle_answers=None, one_question_at_a_time=False,
                                                  apply_negative_scores=None):
        (exam_page_id, entrance_token, device_token) = self.get_ready_an_exam_page_with_answers(institute_id,
                                                                                                entrance_type=ENTRANCE_TYPE_API_INVITE,
                                                                                                start_date=start_date,
                                                                                                end_date=end_date,
                                                                                                duration=duration,
                                                                                                reanswer=reanswer,
                                                                                                apply_negative_scores=apply_negative_scores,
                                                                                                shuffle_questions=shuffle_questions,
                                                                                                shuffle_answers=shuffle_answers,
                                                                                                one_question_at_a_time=one_question_at_a_time,
                                                                                                validate_success=True)

        (response_data, status_code, error_message) = self.tester.post_without_token(
            self.institute_student_exam_page_finish, {
                'device_token': device_token,
                'entrance_token': entrance_token,
            })

        return response_data, status_code, error_message
