# Django
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.shortcuts import reverse
from django.utils.html import format_html, strip_tags
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext as _

# Project
from django_easy_quiz.exporters.saved_quiz_export import xlsx_response
from django_easy_quiz.models import (
    InterpretationQuiz,
    InterpretationQuizAnswer,
    InterpretationQuizConclusion,
    InterpretationQuizQuestion,
    MoreInfoAnswer,
    MoreInfoQuestion,
    MoreInfoQuiz,
    SavedQuiz,
    WeightedAnswersQuiz,
    WeightedAnswersQuizAnswer,
    WeightedAnswersQuizConclusion,
    WeightedAnswersQuizQuestion,
)
from django_easy_quiz.settings import GATHER_STATISTICS
from django_easy_quiz.utils import admin_error_min_max_conclusion


class EasyQuizAdminWideMixin:
    class Media:
        css = {"all": ("django_easy_quiz/css/admin_quiz_wide.css",)}


def nb_of_questions(obj):
    try:
        return str(len(obj.weightedanswersquizquestion_set.all()))
    except AttributeError:
        return str(len(obj.interpretationquizquestion_set.all()))


class WeightedAnswersQuizConclusionAdminInline(admin.StackedInline):
    model = WeightedAnswersQuizConclusion
    extra = 0
    min_num = 1

    fieldsets = (
        (
            None,
            {
                "fields": (
                    ("min_points", "max_points"),
                    "description",
                ),
            },
        ),
    )


class WeightedAnswersQuizQuestionAdminInline(admin.StackedInline):
    model = WeightedAnswersQuizQuestion
    extra = 0
    min_num = 1
    show_change_link = True
    question_number_count = 0
    readonly_fields = ("question_number", "edit_answers_link")
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "question_number",
                    "order",
                    "label",
                    "multiple_answers",
                    "question_explanation_text",
                    "edit_answers_link",
                ),
                "description": _(
                    "Save the quiz, then open the question page to add or edit answers."
                ),
            },
        ),
    )

    @admin.display(description="")
    def question_number(self, obj):
        self.question_number_count += 1
        return _("Question number") + " " + str(self.question_number_count)

    @admin.display(description=_("Answers"))
    def edit_answers_link(self, obj):
        if not obj.pk:
            return _("Save the quiz first.")
        url = reverse(
            "admin:django_easy_quiz_weightedanswersquizquestion_change", args=[obj.pk]
        )
        win = f"waq_answers_{obj.pk}"
        return format_html(
            '<a class="button" href="{url}" '
            "onclick=\"window.open(this.href, '{win}', "
            "'width=1200,height=800,scrollbars=yes,resizable=yes'); "
            'return false;">{label}</a>',
            url=url,
            win=win,
            label=_("Edit answers for this question"),
        )


class WeightedAnswersQuizAnswerAdminInline(admin.StackedInline):
    model = WeightedAnswersQuizAnswer
    extra = 0
    fieldsets = (
        (None, {"fields": ("label",)}),
        (None, {"fields": (("points", "order"),)}),
    )


@admin.register(WeightedAnswersQuizQuestion)
class WeightedAnswersQuizQuestionAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
    list_display = ("id", "quiz", "order", "label", "multiple_answers")
    list_filter = ("quiz", "multiple_answers")
    search_fields = ("label", "quiz__name")
    inlines = [WeightedAnswersQuizAnswerAdminInline]


@admin.register(WeightedAnswersQuiz)
class WeightedAnswersQuizAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
    if GATHER_STATISTICS:
        fieldsets = (
            (
                None,
                {
                    "fields": (
                        "name",
                        "questions_random",
                        "answers_random",
                        "show_score_summary",
                        "show_answers_corrections_text",
                        "description",
                        "more_info_quiz",
                    ),
                },
            ),
        )
    else:
        fieldsets = (
            (
                None,
                {
                    "fields": (
                        "name",
                        "questions_random",
                        "answers_random",
                        "show_score_summary",
                        "show_answers_corrections_text",
                        "description",
                    ),
                },
            ),
        )

    list_display = (
        "id",
        "name",
        nb_of_questions,
        "questions_random",
        "answers_random",
    )
    list_display_links = (
        "id",
        "name",
    )
    list_filter = (
        "questions_random",
        "answers_random",
        "create_date",
    )
    search_fields = ("name",)

    inlines = [
        WeightedAnswersQuizQuestionAdminInline,
        WeightedAnswersQuizConclusionAdminInline,
    ]

    def save_formset(self, request, form, formset, change):
        """
        Save formset, BUT we will launch some checks before:
            - check that no conclusion have min_points > max_points
            - check that all conclusions create only one (enclosed) number sequence and that the min(answers_points_sum) & max(answers_points_sum) are within its bounds
        """
        instances = formset.save(commit=False)
        for instance in instances:
            instance.save()
        # if checkbox "Delete" is ticked then delete questions
        for obj in formset.deleted_objects:
            obj.delete()

        # question formset is treated before conclusion, so in conclusion formset we can get the quiz, the questions, and the conclusion (using the formset.cleaned_data)
        if "WeightedAnswersQuizConclusionFormFormSet" in str(type(formset)):
            min_points_sum = 0
            max_points_sum = 0
            conclusions_combinations = []

            for question in (
                formset[0].cleaned_data["quiz"].weightedanswersquizquestion_set.all()
            ):
                points = [a.points for a in question.answers.all()]
                if not points:
                    messages.error(
                        request,
                        _('Question "%(q)s" must have at least one answer.')
                        % {"q": question.label},
                        extra_tags="easy_quiz_error",
                    )
                    continue
                min_points_sum += min(points)
                max_points_sum += max(points)

            for conclusion in formset.cleaned_data:
                if "min_points" in conclusion and "max_points" in conclusion:
                    if conclusion["min_points"] > conclusion["max_points"]:
                        # there is a problem, so we create a message with an extra tag using this function:
                        admin_error_min_max_conclusion(
                            request,
                            conclusion["min_points"],
                            conclusion["max_points"],
                            conclusion["description"],
                        )
                    conclusions_combinations.append(
                        [conclusion["min_points"], conclusion["max_points"]]
                    )
        # we need to save formset even if it have errors, because django needs its id somewhere later, and we don't want to make the user have to re-type the answers & conclusions
        formset.save_m2m()

    def response_add(self, request, obj, post_url_continue=None):
        """
        Method to get url to redirect after having created a new quiz:
            - Redirect to list view (using super) if there's no message with extra tag "easy_quiz_error".
            - Redirect to change view if a message with extra tag "easy_quiz_error" exist.
        """
        storage = messages.get_messages(request)
        for message in storage:
            if message.extra_tags == "easy_quiz_error":
                storage.used = False
                return HttpResponseRedirect(
                    reverse(
                        "admin:{}_{}_change".format(
                            obj._meta.app_label, obj._meta.model_name
                        ),
                        args=[obj.id],
                    )
                )
        storage.used = False
        return super().response_change(request, obj)

    def response_change(self, request, obj):
        """
        Method to get url to redirect after having modified a quiz:
            - Redirect to list view (using super) if there's no message with extra tag "easy_quiz_error".
            - Redirect to change view if a message with extra tag "easy_quiz_error" exist.
        """
        storage = messages.get_messages(request)
        for message in storage:
            if message.extra_tags == "easy_quiz_error":
                storage.used = False
                return HttpResponseRedirect(
                    reverse(
                        "admin:{}_{}_change".format(
                            obj._meta.app_label, obj._meta.model_name
                        ),
                        args=[obj.id],
                    )
                )
        storage.used = False

        return super().response_change(request, obj)


# InterpretationQuiz


class InterpretationQuizConclusionAdminInline(admin.TabularInline):
    model = InterpretationQuizConclusion
    extra = 0
    min_num = 1


class InterpretationQuizQuestionAdminInline(admin.StackedInline):
    model = InterpretationQuizQuestion
    extra = 0
    min_num = 1
    show_change_link = True
    question_number_count = 0
    readonly_fields = ("question_number", "edit_answers_link")
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "question_number",
                    "order",
                    "label",
                    "edit_answers_link",
                ),
                "description": _(
                    "Save the quiz, then open the question page to add or edit answers."
                ),
            },
        ),
    )

    @admin.display(description="")
    def question_number(self, obj):
        self.question_number_count += 1
        return _("Question number") + " " + str(self.question_number_count)

    @admin.display(description=_("Answers"))
    def edit_answers_link(self, obj):
        if not obj.pk:
            return _("Save the quiz first.")
        url = reverse(
            "admin:django_easy_quiz_interpretationquizquestion_change", args=[obj.pk]
        )
        win = f"iq_answers_{obj.pk}"
        return format_html(
            '<a class="button" href="{url}" '
            "onclick=\"window.open(this.href, '{win}', "
            "'width=1200,height=800,scrollbars=yes,resizable=yes'); "
            'return false;">{label}</a>',
            url=url,
            win=win,
            label=_("Edit answers for this question"),
        )


class InterpretationQuizAnswerAdminInline(admin.TabularInline):
    model = InterpretationQuizAnswer
    extra = 0


@admin.register(InterpretationQuizQuestion)
class InterpretationQuizQuestionAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
    list_display = ("id", "quiz", "order", "label")
    list_filter = ("quiz",)
    search_fields = ("label", "quiz__name")
    inlines = [InterpretationQuizAnswerAdminInline]


@admin.register(InterpretationQuiz)
class InterpretationQuizAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
    if GATHER_STATISTICS:
        fieldsets = (
            (
                None,
                {
                    "fields": (
                        "name",
                        "questions_random",
                        "answers_random",
                        "description",
                        "more_info_quiz",
                    ),
                },
            ),
        )
    else:
        fieldsets = (
            (
                None,
                {
                    "fields": (
                        "name",
                        "questions_random",
                        "answers_random",
                        "description",
                    ),
                },
            ),
        )

    list_display = (
        "id",
        "name",
        nb_of_questions,
        "questions_random",
        "answers_random",
    )
    list_display_links = (
        "id",
        "name",
    )
    list_filter = (
        "questions_random",
        "answers_random",
        "create_date",
    )
    search_fields = ("name",)

    inlines = [
        InterpretationQuizQuestionAdminInline,
        InterpretationQuizConclusionAdminInline,
    ]


# activate stats only if activated in settings (default = False)
if GATHER_STATISTICS:

    @admin.register(SavedQuiz)
    class SavedQuizAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
        actions = ("export_saved_quiz_xlsx",)

        def has_change_permission(self, request, obj=None):
            return False

        def has_add_permission(self, request, obj=None):
            return False

        readonly_fields = [field.name for field in SavedQuiz._meta.get_fields()]

        list_display = ("quiz_type", "started_quiz", "quiz_ended", "uuid")
        list_filter = (
            "quiz_ended",
            "weighted_answers_quiz",
            "interpretation_quiz",
        )

        fields = (
            "quiz_type",
            "quiz_ended",
            "answers_admin",
            "conclusion_points_admin",
            "conclusion_admin",
            "more_infos_admin",
        )

        @admin.display(description=_("Answers"))
        def answers_admin(self, obj):
            answers_txt = "<ul>"
            for answers in obj.answers:
                answers_txt += f"<li>{answers['question']['label']}<ul>"
                if "answers" in answers:  # weighted answers quiz
                    for answer in answers["answers"]:
                        if isinstance(answer, dict):
                            if answer.get("selected"):
                                answers_txt += (
                                    f"<li>{strip_tags(answer.get('label', ''))}</li>"
                                )
                        else:
                            answers_txt += f"<li>{strip_tags(answer)}</li>"
                    question_explanation_text = answers.get(
                        "question_explanation_text"
                    ) or answers["question"].get("question_explanation_text")
                    if question_explanation_text:
                        answers_txt += (
                            f"<li><i>{strip_tags(question_explanation_text)}</i></li>"
                        )
                else:
                    if "answer" in answers:
                        answers_txt += f"<li>{strip_tags(answers['answer'])}</li>"
                    else:
                        for key in ("answer_1", "answer_2", "answer_3", "answer_4"):
                            if key in answers.get("question", {}):
                                answers_txt += (
                                    f"<li>{strip_tags(answers['question'][key])}</li>"
                                )
                answers_txt += "</ul></li>"
            if answers_txt == "<ul>":
                answers_txt += f"<li>{_('No answer recorded for this quiz.')}</li>"
            answers_txt += "</ul>"
            return mark_safe(answers_txt)

        @admin.display(description=_("Points"))
        def conclusion_points_admin(self, obj):
            if "points" in obj.conclusion:  # weighted_answers quiz
                return obj.conclusion["points"]
            return ""  # interpretation quiz

        @admin.display(description=_("Conclusion"))
        def conclusion_admin(self, obj):
            if "description" in obj.conclusion:  # weighted_answers quiz
                return mark_safe(obj.conclusion["description"])
            return mark_safe(obj.conclusion)  # interpretation quiz

        @admin.display(description=_("More infos"))
        def more_infos_admin(self, obj):
            more_infos_txt = "<ul>"
            for question in obj.more_infos:
                more_infos_txt += f"<li>{question['question']}<ul>"
                more_infos_txt += f"<li>{question['answer']}</li>"
                more_infos_txt += "</ul></li>"
            return mark_safe(more_infos_txt)

        @admin.action(description=_("Export selected saved quizzes as Excel (XLSX)"))
        def export_saved_quiz_xlsx(self, request, queryset):
            saved_quizzes = list(
                queryset.filter(quiz_ended=True).select_related(
                    "weighted_answers_quiz", "interpretation_quiz"
                )
            )
            timestamp = now().strftime("%d-%m-%Y-%H_%M_%S")
            filename = f"{_('Export saved quizzes')} {timestamp}.xlsx"
            return xlsx_response(saved_quizzes, filename)


# activate more info questions only if activated in settings (default = False)
if GATHER_STATISTICS:

    class MoreInfoAnswerAdminInline(admin.StackedInline):
        model = MoreInfoAnswer

    @admin.register(MoreInfoQuestion)
    class MoreInfoQuestionAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
        inlines = [
            MoreInfoAnswerAdminInline,
        ]

        readonly_fields = ("quiz",)

    class MoreInfoQuestionAdminInline(admin.StackedInline):
        model = MoreInfoQuestion
        show_change_link = True

    @admin.register(MoreInfoQuiz)
    class MoreInfoQuizAdmin(EasyQuizAdminWideMixin, admin.ModelAdmin):
        fieldsets = (
            (
                None,
                {
                    "fields": (("name"),),
                },
            ),
            (
                None,
                {
                    "fields": [],
                    "description": _(
                        "Save your questions first, and then you will be able to update each question to add answers."
                    ),
                },
            ),
        )

        inlines = [
            MoreInfoQuestionAdminInline,
        ]
