import math
import secrets
import string

from exam.models import EXAM_PAGE_STATE_NOT_ATTENDED
from payment.models import QuizPurchase, WikiazmaBalanceHistory, InstituteBalanceHistory, UserBalanceHistory
from plan.models import Environment
from question.models import QUESTION_TYPE_MULTIPLE_CHOICE, QUESTION_TYPE_DESCRIPTIVE
from quiz.blueprint.blueprint import Blueprint
from quiz.blueprint.sheet import Sheet
from quiz.models import QuestionScore
from quiz.models import QuizSheet
from utils.utils import logs_adder


def create_quiz_sheet_helper(quiz, user=None, institute_student=None, allowed_start_at=None, allowed_finish_at=None,
                             duration=None):
    blueprint = Blueprint.from_dict(quiz._blueprint)

    if institute_student is not None:
        alphabet = string.ascii_letters + string.digits
        entrance_token = ''.join(secrets.choice(alphabet) for i in range(60))
        while QuizSheet.objects.filter(entrance_token=entrance_token):
            entrance_token = ''.join(secrets.choice(alphabet) for i in range(60))
    else:
        entrance_token = None

    sheet = Sheet.from_blurprint(blueprint)

    quiz_sheet = QuizSheet.objects.create(entrance_token=entrance_token, user=user, institute_student=institute_student,
                                          quiz=quiz, state=EXAM_PAGE_STATE_NOT_ATTENDED, last_quizee_input_index=0,
                                          allowed_start_at=allowed_start_at if allowed_start_at else quiz.start,
                                          allowed_finish_at=allowed_finish_at if allowed_finish_at else quiz.end,
                                          duration=duration if duration else quiz.duration,
                                          _questions_sheet=sheet.question_sheet.to_dict(),
                                          _solutions_sheet=sheet.solutions_sheet.to_dict())

    return quiz_sheet


def correct_multiple_choice_question_helper(user_input, question, solution, quiz_sheet, correction=None, force_score=None):
    if correction is None:
        correction = QuestionScore()

    correction.corrector = None
    correction.quiz_sheet = quiz_sheet
    correction.question_id = solution['question_id']
    correction.question_index = question['index']

    if user_input is None:
        correction.blank = True

        correct_choices = 0
        wrong_choices = 0
        blank_choices = question['input_rules']['max_selectable_choices']
        question_score = 0
    else:
        correction.blank = False
        correct_choices = 0
        wrong_choices = 0

        for user_choice in user_input['selected_choices']:
            user_choice_found = False
            for choice in question['choices']:
                if choice == user_choice:
                    user_choice_found = True
                    if choice in solution['correct_choices']:
                        correct_choices += 1
                    else:
                        wrong_choices += 1
                    break
            if not user_choice_found:
                print("critical error #4817341")

        blank_choices = question['input_rules']['max_selectable_choices'] - (correct_choices + wrong_choices)

        total_number_of_correct_choices = 0
        total_number_of_wrong_choices = 0
        for choice in question['choices']:
            if choice in solution['correct_choices']:
                total_number_of_correct_choices += 1
            else:
                total_number_of_wrong_choices += 1

        positive_score = 0
        if total_number_of_correct_choices != 0:
            positive_score = correct_choices / min(total_number_of_correct_choices,
                                                   question['input_rules']['max_selectable_choices']) * question['positive_score']

        negative_score = 0
        if quiz_sheet.quiz.apply_negative_scores:
            # TODO max_selectable_choices should be equal to the number of correct choices otherwise, user can
            # TODO select all choices and still get the max positive score
            if total_number_of_wrong_choices != 0:
                negative_score = wrong_choices / min(total_number_of_wrong_choices,
                                                     question['input_rules']['max_selectable_choices']) * question[
                                     'negative_score']

        question_score = positive_score - negative_score

    correction.score = question_score if force_score is None else force_score
    correction.correct_choices = correct_choices
    correction.wrong_choices = wrong_choices
    correction.blank_choices = blank_choices
    correction.save()

    return correction


def auto_correct_helper(quiz_sheet):
    corrections_list = []
    for question in quiz_sheet._questions_sheet['questions']:
        solution = quiz_sheet.get_solution_by_index(question['index'])
        if quiz_sheet._quizee_inputs_sheet is None:
            user_input = None
        else:
            user_inputs = [user_input for user_input in quiz_sheet._quizee_inputs_sheet if
                           user_input['index'] == question['index']]
            user_input = user_inputs[0] if len(user_inputs) != 0 else None

        if question['question_type'] == QUESTION_TYPE_MULTIPLE_CHOICE:
            correction = correct_multiple_choice_question_helper(user_input, question, solution, quiz_sheet)
            corrections_list.append(correction)
        elif question['question_type'] == QUESTION_TYPE_DESCRIPTIVE:
            if user_input == None:
                correction = QuestionScore.objects.create(quiz_sheet=quiz_sheet, corrector=None, question_index=question['index'],
                                                          question_id=solution['question_id'], blank=True, correct_choices=None,
                                                          wrong_choices=None, blank_choices=None, score=0, )
                corrections_list.append(correction)
        else:
            # TODO critical error
            raise ValueError('Unknown question type')

    return corrections_list


def generate_report_card_helper(quiz_sheet, corrections_list):
    report_card = {}
    total_score = 0

    groups = quiz_sheet._questions_sheet['groups']
    if len(groups) == 0:
        groups = [{'name': '', 'start_at': 0}]

    question_start_ats = []
    for group in groups:
        question_start_ats.append(group['start_at'])
    question_start_ats.append(len(quiz_sheet._questions_sheet['questions']))

    report_card_groups = []
    for index, group in enumerate(groups):

        correct_choices = 0
        wrong_choices = 0
        blank_choices = 0
        positive_scores = 0
        negative_scores = 0
        durations = 0
        questions_count = 0
        score = 0
        for question in quiz_sheet._questions_sheet['questions'][question_start_ats[index]:question_start_ats[index + 1]]:
            target_corrections = [item for item in corrections_list if item.question_index == question['index']]
            if not target_corrections:
                return None, None, None

            target_corrections.sort(key=lambda item: item.modified_at, reverse=True)
            selected_correction = target_corrections[0]
            # modify total score
            total_score += selected_correction.score
            score += selected_correction.score

            # blank_choices is different from blank_questions
            # for example, a question may have max_selectable_choices=5 and user may have selected 2 of 5 choices
            # selected_correction.blank will be False and selected_correction.blank_choices will be 3
            # selected_correction.blank will track blank_questions
            # selected_correction.blank_choices will track blank_choices
            # if selected_correction.blank:
            #     report_card_group_item['blank_choices'] += 1

            if selected_correction.correct_choices is not None:
                correct_choices += selected_correction.correct_choices
            if selected_correction.wrong_choices is not None:
                wrong_choices += selected_correction.wrong_choices
            if selected_correction.blank_choices is not None:
                blank_choices += selected_correction.blank_choices

            positive_scores += question['positive_score']
            negative_scores += question['negative_score']
            durations += question['duration']
            questions_count += 1

        if correct_choices + wrong_choices + blank_choices != 0:
            percent_with_negatives = (correct_choices * 3 - wrong_choices) * 100 / 3 / (
                    correct_choices + wrong_choices + blank_choices)
            percent_without_negatives = correct_choices * 100 / (correct_choices + wrong_choices + blank_choices)
        else:
            percent_with_negatives = 0
            percent_without_negatives = 0

        report_card_group_item = {'name': group['name'], 'positive_scores': positive_scores, 'negative_scores': negative_scores,
                                  'durations': durations, 'questions_count': questions_count, 'correct_choices': correct_choices,
                                  'wrong_choices': wrong_choices, 'blank_choices': blank_choices, 'score': score,
                                  'percent_with_negatives': percent_with_negatives,
                                  'percent_without_negatives': percent_without_negatives}

        report_card_groups.append(report_card_group_item)

    report_card['groups'] = report_card_groups

    overall = {"positive_scores": sum(group['positive_scores'] for group in report_card_groups),
               "negative_scores": sum(group['negative_scores'] for group in report_card_groups),
               "durations": sum(group['durations'] for group in report_card_groups),
               "questions_count": sum(group['questions_count'] for group in report_card_groups),
               "correct_choices": sum(group['correct_choices'] for group in report_card_groups),
               "wrong_choices": sum(group['wrong_choices'] for group in report_card_groups),
               "blank_choices": sum(group['blank_choices'] for group in report_card_groups), "score": total_score}

    report_card['overall'] = overall

    # if len(quiz_sheet._questions_sheet['groups']) == 0:
    #     report_card['groups'] = []

    return report_card, total_score, overall['positive_scores']


def price_calculator_helper(user, institute_student, quiz):
    # TODO institute_student not supported yet
    if user:
        user_min_wage_fee = user.best_min_wage_fee
        user_wage_factor = user.best_wage_factor
        user_tax_factor = user.best_tax_factor

        wage = quiz.price * user_wage_factor
        if wage < user_min_wage_fee:
            wage = user_min_wage_fee
        tax_factor = user_tax_factor
    else:
        active_env, _ = Environment.objects.get_or_create(active_env=True)

        active_env_min_wage_fee = active_env.default_min_wage_fee
        active_env_wage_factor = active_env.default_wage_factor
        active_env_tax_factor = active_env.default_tax_factor

        wage = quiz.price * active_env_wage_factor
        if wage < active_env_min_wage_fee:
            wage = active_env_min_wage_fee
        tax_factor = active_env_tax_factor

    tax = (quiz.price + wage) * tax_factor

    if quiz.wage_free:
        wage = 0

    if quiz.vax_free:
        tax = 0

    total_cost = math.floor(quiz.price + wage + tax)  # all these numbers should be integers
    institute_share = math.ceil(quiz.price)  # all these numbers should be integers
    wikiazma_share = total_cost - institute_share  # all these numbers should be integers

    return total_cost, institute_share, wikiazma_share, wage, tax


def quiz_purchase_wikiazma_balance_handler(quiz, reason_meta_data, user, amount):
    last_wikiazma_balance_history = WikiazmaBalanceHistory.get_last_wikiazma_balance()
    new_wikiazma_balance_history = WikiazmaBalanceHistory(amount=amount, reason_meta_data=reason_meta_data, reason=QuizPurchase,
                                                          quiz=quiz, user=user)
    new_wikiazma_balance_history.remaining_balance = last_wikiazma_balance_history.remaining_balance + new_wikiazma_balance_history.amount
    new_wikiazma_balance_history.save()


def quiz_purchase_institute_balance_handler(institute, quiz, reason_meta_data, amount):
    last_institute_balance_history = InstituteBalanceHistory.get_last_institute_balance(institute)
    new_balance_history = InstituteBalanceHistory(institute=institute, amount=amount, reason_meta_data=reason_meta_data,
                                                  reason=QuizPurchase, quiz=quiz)
    new_balance_history.remaining_balance = last_institute_balance_history.remaining_balance + new_balance_history.amount
    new_balance_history.save()


def quiz_purchase_user_balance_handler(user, quiz, reason_meta_data, amount):
    last_user_balance_history = UserBalanceHistory.get_last_user_balance(user)
    new_balance_history = UserBalanceHistory(user=user, amount=amount, reason_meta_data=reason_meta_data, reason=QuizPurchase,
                                             quiz=quiz)
    new_balance_history.remaining_balance = last_user_balance_history.remaining_balance + new_balance_history.amount
    new_balance_history.save()

    if new_balance_history.remaining_balance < 0:
        # TODO critical error
        print(f"amount={amount}, new_balance_history.remaining_balance={new_balance_history.remaining_balance}")
        print("critical error # 94166237225")
        pass


def quiz_purchase_handler(user, quiz, quiz_sheet):
    total_cost, institute_share, wikiazma_share, wage, tax = price_calculator_helper(user, None, quiz)

    if total_cost != 0:
        quiz_purchase_institute_balance_handler(institute=quiz.institute, amount=institute_share, quiz=quiz,
                                                reason_meta_data={'quiz_sheet_id': str(quiz_sheet.referer_id)})
        quiz_purchase_wikiazma_balance_handler(quiz=quiz, reason_meta_data={'quiz_sheet_id': str(quiz_sheet.referer_id)},
                                               user=user, amount=wikiazma_share)
        quiz_purchase_user_balance_handler(user=user, quiz=quiz, reason_meta_data={'quiz_sheet_id': str(quiz_sheet.referer_id)},
                                           amount=-1 * total_cost)


list_quiz_sort_by_switcher = {'name_asc': 'name', 'name_desc': '-name', 'created_at_asc': 'created_at',
                              'created_at_desc': '-created_at', 'modified_at_asc': 'modified_at',
                              'modified_at_desc': '-modified_at', 'questions_count_asc': 'questions_count',
                              'questions_count_desc': '-questions_count', 'start_asc': 'start', 'start_desc': '-start',
                              'duration_asc': 'duration', 'duration_desc': '-duration'}

list_quiz_sheet_sort_by_switcher = {'entrance_date_asc': 'entrance_date', 'entrance_date_desc': '-entrance_date',
                                    'duration_asc': 'duration',
                                    'duration_desc': '-duration', 'final_score_asc': 'final_score',
                                    'final_score_desc': '-final_score', 'state_asc': 'state', 'state_desc': '-state'}
