from flask import flash, redirect, request, url_for
from flask_admin import Admin as FlaskAdmin
from flask_admin import expose
from flask_admin.contrib.sqla.fields import QuerySelectField
from flask_admin.menu import MenuLink
from flask_admin.model.template import EndpointLinkRowAction
from flask_login import current_user
from flask_security.changeable import admin_change_password
from flask_wtf import FlaskForm
from markupsafe import Markup
from wtforms.validators import DataRequired

from .advlabdb_independent_funs import (
    deep_getattr,
    experiment_marks_missing_formatter,
    flashRandomPassword,
    str_formatter,
)
from .custom_classes import (
    SecureAssistantBaseView,
    SecureAssistantIndexView,
    SecureAssistantModelView,
)
from .exceptions import ModelViewException
from .forms import assistant_group_experiment_form_factory
from .model_dependent_funs import (
    generate_new_password_field,
    parse_selection_mark_field,
    user_info_fields,
)
from .model_independent_funs import randomPassword, reportBadAttempt
from .models import Assistant, GroupExperiment, Semester, SemesterExperiment, User, db

assistantSpace = FlaskAdmin(
    name="Assistant@AdvLabDB",
    url="/assistant",
    template_mode="bootstrap4",
    index_view=SecureAssistantIndexView(name="Home", url="/assistant", endpoint="assistant"),
)


class AssistantGroupExperimentView(SecureAssistantModelView):
    def is_accessible(self):
        if not super().is_accessible():
            return False

        active_semester = current_user.active_semester

        if active_semester.done:
            semester_changed = current_user.set_last_semester_as_active()

            if not semester_changed:
                flash(
                    Markup(
                        f"Active semester {active_semester} is set as done. Therefore, you are not allowed to view or edit any marks in this semester. You should change your active semester in <a href='/user-settings'>user settings</a> if possible."
                    ),
                    "danger",
                )
                return False

        return True

    column_display_actions = True

    column_list = [
        "semester_experiment.experiment",
        "group.number",
        "group.part_students",
        "appointments",
        "experiment_marks_missing",
        "note",
    ]
    column_labels = {
        "semester_experiment.experiment": "Experiment",
        "group.number": "Group number",
        "group.part_students": "Students",
    }

    column_default_sort = ("experiment_marks_missing", True)

    column_extra_row_actions = [
        EndpointLinkRowAction(
            icon_class="fa fa-pencil",
            endpoint="assistant_group_experiment.form_view",
            title="Edit",
        )
    ]

    @staticmethod
    def part_students_formatter(view, context, model, name):
        part_students = deep_getattr(model, name)
        if part_students is not None:
            return ", ".join(str(part_student.student) for part_student in part_students)

        return ""

    @staticmethod
    def appointments_formatter(view, context, model, name):
        appointments = deep_getattr(model, name)
        if appointments is not None:
            return ", ".join(str(appointment.date) for appointment in appointments)

        return ""

    column_formatters = {
        "semester_experiment.experiment": str_formatter,
        "group.part_students": part_students_formatter,
        "appointments": appointments_formatter,
        "experiment_marks_missing": experiment_marks_missing_formatter,
    }

    def query_modifier(self, query):
        return (
            query.join(SemesterExperiment)
            .where(SemesterExperiment.semester == current_user.active_semester)
            .join(SemesterExperiment.assistants)
            .where(Assistant.user == current_user)
        )

    @expose("/form/", methods=("GET", "POST"))
    def form_view(self):
        group_experiment_id_str = request.args.get("id")

        try:
            group_experiment = db.session.get(GroupExperiment, int(group_experiment_id_str))
        except Exception:
            red = url_for("main.index") + "assistant/group_experiment"
            flash("No valid group experiment id")
            return redirect(red)

        if group_experiment not in self.get_query():
            reportBadAttempt("Assistant {current_user} tried to edit {group_experiment}")
            self.handle_view_exception(ModelViewException("Unauthorized action!"))
            return redirect(self.url)

        form, appointments, experiment_marks = assistant_group_experiment_form_factory(current_user, group_experiment)

        num_appointments = len(appointments)
        appointment_fields = [
            getattr(form, f"appointment_{appointment_num}") for appointment_num in range(1, num_appointments + 1)
        ]

        num_experiment_marks = len(experiment_marks)
        experiment_mark_students = [experiment_mark.part_student.student for experiment_mark in experiment_marks]
        oral_experiment_mark_fields = [
            getattr(form, f"oral_experiment_mark_{experiment_mark_num}")
            for experiment_mark_num in range(1, num_experiment_marks + 1)
        ]
        protocol_experiment_mark_fields = [
            getattr(form, f"protocol_experiment_mark_{experiment_mark_num}")
            for experiment_mark_num in range(1, num_experiment_marks + 1)
        ]

        if form.validate_on_submit():
            any_final_experiment_mark_changed = False
            try:
                for ind, appointment in enumerate(appointments):
                    appointment.date = appointment_fields[ind].data

                for ind, experiment_mark in enumerate(experiment_marks):
                    form_oral_mark = parse_selection_mark_field(oral_experiment_mark_fields[ind])
                    form_protocol_mark = parse_selection_mark_field(protocol_experiment_mark_fields[ind])

                    if (
                        form_oral_mark != experiment_mark.oral_mark
                        or form_protocol_mark != experiment_mark.protocol_mark
                    ):
                        final_experiment_mark_changed = experiment_mark.set_oral_protocol_mark(
                            form_oral_mark,
                            form_protocol_mark,
                            call_update_experiment_marks_missing=False,
                        )
                        if final_experiment_mark_changed:
                            any_final_experiment_mark_changed = True

                        experiment_mark.assistant = current_user.assistant

                if any_final_experiment_mark_changed:
                    group_experiment.update_experiment_marks_missing()

                group_experiment.note = form.note.data

                db.session.commit()

                red = url_for("main.index") + "assistant/group_experiment"
                return redirect(red)
            except Exception as ex:
                flash(str(ex), "error")

                db.session.rollback()

        final_experiment_marks = [experiment_mark.final_experiment_mark for experiment_mark in experiment_marks]

        return self.render(
            "assistant_group_experiment_form.jinja.html",
            form=form,
            semester_experiment=group_experiment.semester_experiment,
            group_number=group_experiment.group.number,
            appointment_fields=appointment_fields,
            experiment_mark_zip=zip(
                experiment_mark_students,
                oral_experiment_mark_fields,
                protocol_experiment_mark_fields,
                final_experiment_marks,
            ),
        )


class AssistantUserView(SecureAssistantModelView):
    class EditForm(FlaskForm):
        @staticmethod
        def semesterQueryFactory():
            # Show only last two semesters to assistants
            return Semester.query.order_by(Semester.id.desc()).where(Semester.done == False).limit(2)

        active_semester = QuerySelectField(
            "Active Semester",
            query_factory=semesterQueryFactory,
            validators=[DataRequired()],
            default=Semester.lastSemester,
            description="You should change the active semester to the last semester. Do not forget to click save! Only last two semesters are shown that are not set as done.",
        )

        phone_number, mobile_phone_number, building, room = user_info_fields()

        generate_new_password = generate_new_password_field()

    can_edit = True
    can_view_details = True
    column_display_actions = True

    column_sortable_list: list[str] = []

    column_list = [
        "email",
        "phone_number",
        "mobile_phone_number",
        "building",
        "room",
        "assistant.semester_experiments",
    ]
    column_labels = {
        "assistant.semester_experiments": "Semester Experiments",
    }

    def query_modifier(self, query):
        return query.where(User.id == current_user.id)

    def on_model_change(self, form, model, is_created):
        if form.generate_new_password.data:
            password = randomPassword()
            flashRandomPassword(model.email, password)

            admin_change_password(model, password, notify=False)  # Password is automatically hashed with this function


class AssistantDocsView(SecureAssistantBaseView):
    @expose("/")
    def index(self):
        return self.render("docs/docs.jinja.html", role="assistant")


def init_assistant_model_views(app):
    assistantSpace.add_view(AssistantGroupExperimentView(GroupExperiment, url="group_experiment"))
    assistantSpace.add_view(AssistantDocsView(name="Docs", url="docs"))

    # Don't add to menu
    # Has to be placed before assistantSpace.init_app
    assistantSpace._views.append(AssistantUserView(User, url="user"))

    assistantSpace.add_link(MenuLink(name="User settings", url="/user-settings"))
    assistantSpace.add_link(MenuLink(name="Logout", url="/logout"))

    assistantSpace.init_app(app)