# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@gmail.com
#
##############################################################################

"""Formulaire ajout/édition d'une évaluation
"""
import datetime
import time

import flask
from flask import url_for, render_template
from flask import g
from flask_login import current_user
from flask import request

from app import db
from app.models import Evaluation, Module, ModuleImpl
from app.models.evaluations import heure_to_time

import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations
from app.scodoc import sco_preferences


def evaluation_create_form(
    moduleimpl_id=None,
    evaluation_id=None,
    edit=False,
    page_title="Évaluation",
):
    "Formulaire création/édition d'une évaluation (pas de ses notes)"
    evaluation: Evaluation
    if evaluation_id is not None:
        evaluation = db.session.get(Evaluation, evaluation_id)
        if evaluation is None:
            raise ScoValueError("Cette évaluation n'existe pas ou plus !")
        moduleimpl_id = evaluation.moduleimpl_id
    #
    modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
    formsemestre_id = modimpl.formsemestre_id
    formsemestre = modimpl.formsemestre
    module: Module = modimpl.module
    sem_ues = formsemestre.get_ues(with_sport=False)
    is_malus = module.module_type == ModuleType.MALUS
    is_apc = module.module_type in (ModuleType.RESSOURCE, ModuleType.SAE)
    preferences = sco_preferences.SemPreferences(formsemestre.id)
    can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
    min_note_max = scu.NOTES_PRECISION  # le plus petit bareme possible
    #
    if not modimpl.can_edit_evaluation(current_user):
        return f"""
        {html_sco_header.sco_header()}
        <h2>Opération non autorisée</h2>
        <p>Modification évaluation impossible pour {current_user.get_nomplogin()}</p>
        <p><a href="{url_for('notes.moduleimpl_status',
            scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
            }" class="stdlink">Revenir</a>
        </p>
        {html_sco_header.sco_footer()}
        """
    if not edit:
        # création nouvel
        if moduleimpl_id is None:
            raise ValueError("missing moduleimpl_id parameter")
        numeros = [(e.numero or 0) for e in modimpl.evaluations]
        initvalues = {
            "jour": time.strftime("%d/%m/%Y", time.localtime()),
            "note_max": 20,
            "numero": (max(numeros) + 1) if numeros else 0,
            "publish_incomplete": is_malus,
            "visibulletin": 1,
        }
        submitlabel = "Créer cette évaluation"
        action = "Création d'une évaluation"
        link = ""
    else:
        # édition données existantes
        # setup form init values
        if evaluation_id is None:
            raise ValueError("missing evaluation_id parameter")
        initvalues = evaluation.to_dict()
        moduleimpl_id = initvalues["moduleimpl_id"]
        submitlabel = "Modifier les données"
        action = "Modification d'une évaluation"
        link = ""
        # Note maximale actuelle dans cette éval ?
        etat = sco_evaluations.do_evaluation_etat(evaluation_id)
        if etat["maxi_num"] is not None:
            min_note_max = max(scu.NOTES_PRECISION, etat["maxi_num"])
        else:
            min_note_max = scu.NOTES_PRECISION
    #
    if min_note_max > scu.NOTES_PRECISION:
        min_note_max_str = scu.fmt_note(min_note_max)
    else:
        min_note_max_str = "0"

    initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
    vals = scu.get_request_args()
    #
    ue_coef_dict = modimpl.module.get_ue_coef_dict()
    if is_apc:  # BUT: poids vers les UE
        if edit:
            if evaluation.set_default_poids():
                db.session.commit()
            ue_poids_dict = evaluation.get_ue_poids_dict()
            for ue in sem_ues:
                initvalues[f"poids_{ue.id}"] = ue_poids_dict[ue.id]
        else:
            for ue in sem_ues:
                coef_ue = ue_coef_dict.get(ue.id, 0.0) or 0.0
                if coef_ue > 0:
                    poids = 1.0  # par défaut au départ
                else:
                    poids = 0.0
                initvalues[f"poids_{ue.id}"] = poids
    #
    form = [
        ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
        ("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
        ("moduleimpl_id", {"default": moduleimpl_id, "input_type": "hidden"}),
        ("numero", {"default": initvalues["numero"], "input_type": "hidden"}),
        (
            "jour",
            {
                "input_type": "datedmy",
                "title": "Date",
                "size": 12,
                "explanation": "date de l'examen, devoir ou contrôle",
            },
        ),
        (
            "heure_debut",
            {
                "title": "Heure de début",
                "explanation": "heure du début de l'épreuve",
                "input_type": "time",
            },
        ),
        (
            "heure_fin",
            {
                "title": "Heure de fin",
                "explanation": "heure de fin de l'épreuve",
                "input_type": "time",
            },
        ),
    ]
    if is_malus:  # pas de coefficient
        form.append(("coefficient", {"input_type": "hidden", "default": "1."}))
    elif not is_apc:  # modules standard hors BUT
        form.append(
            (
                "coefficient",
                {
                    "size": 6,
                    "type": "float",  # peut être négatif (!)
                    "explanation": "coef. dans le module (choisi librement par l'enseignant, non utilisé pour rattrapage et 2ème session)",
                    "allow_null": False,
                },
            )
        )
    form += [
        (
            "note_max",
            {
                "size": 4,
                "type": "float",
                "title": "Notes de 0 à",
                "explanation": f"barème (note max actuelle: {min_note_max_str})",
                "allow_null": False,
                "max_value": scu.NOTES_MAX,
                "min_value": min_note_max,
            },
        ),
        (
            "description",
            {
                "size": 36,
                "type": "text",
                "explanation": """type d'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".""",
            },
        ),
        (
            "visibulletin",
            {
                "input_type": "boolcheckbox",
                "title": "Visible sur bulletins",
                "explanation": "(pour les bulletins en version intermédiaire/passerelle)",
            },
        ),
        (
            "publish_incomplete",
            {
                "input_type": "boolcheckbox",
                "title": "Prise en compte immédiate",
                "explanation": "notes utilisées même si incomplètes",
            },
        ),
        (
            "evaluation_type",
            {
                "input_type": "menu",
                "title": "Modalité",
                "allowed_values": (
                    scu.EVALUATION_NORMALE,
                    scu.EVALUATION_RATTRAPAGE,
                    scu.EVALUATION_SESSION2,
                ),
                "type": "int",
                "labels": (
                    "Normale",
                    "Rattrapage (remplace si meilleure note)",
                    "Deuxième session (remplace toujours)",
                ),
            },
        ),
    ]
    if is_apc:  # ressources et SAÉs
        form += [
            (
                "coefficient",
                {
                    "size": 6,
                    "type": "float",
                    "explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
                    "allow_null": False,
                },
            ),
        ]
        # Liste des UE utilisées dans des modules de ce semestre:
        if sem_ues and not can_edit_poids:
            form.append(
                (
                    "sep_poids",
                    {
                        "input_type": "separator",
                        "title": """
                    <div class="warning-light">les poids ne sont pas modifiables (voir réglage paramétrage)
                    </div>""",
                    },
                )
            )
        for ue in sem_ues:
            coef_ue = ue_coef_dict.get(ue.id, 0.0)
            form.append(
                (
                    f"poids_{ue.id}",
                    {
                        "title": f"Poids {ue.acronyme}",
                        "size": 2,
                        "type": "float",
                        "explanation": f"""
                            <span class="eval_coef_ue" title="coef. du module dans cette UE">({
                                "coef. mod.:" +str(coef_ue) if coef_ue
                                else "ce module n'a pas de coef. dans cette UE"
                            })</span>
                            <span class="eval_coef_ue_titre">{ue.titre or ''}</span>
                        """,
                        "allow_null": False,
                        # ok si poids nul ou coef vers l'UE nul:
                        "validator": lambda val, field: (not val)
                        or ue_coef_dict.get(int(field[len("poids_") :]), 0.0) != 0,
                        "enabled": (coef_ue != 0 or initvalues[f"poids_{ue.id}"] != 0.0)
                        and can_edit_poids,
                    },
                ),
            )
    tf = TrivialFormulator(
        request.base_url,
        vals,
        form,
        cancelbutton="Annuler",
        submitlabel=submitlabel,
        initvalues=initvalues,
        readonly=False,
    )

    dest_url = url_for(
        "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id
    )
    H = [
        f"""<h3>{action} en
        {scu.MODULE_TYPE_NAMES[module.module_type]} <a class="stdlink" href="{
            url_for("notes.moduleimpl_status",
                scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
            }">{module.code or "module sans code"} {
                module.titre or module.abbrev or "(sans titre)"
            }</a> {link}</h3>
        """
    ]
    if tf[0] == 0:
        head = html_sco_header.sco_header(page_title=page_title)
        return (
            head
            + "\n".join(H)
            + "\n"
            + tf[1]
            + render_template("scodoc/help/evaluations.j2", is_apc=is_apc)
            + render_template("sco_timepicker.j2")
            + html_sco_header.sco_footer()
        )
    elif tf[0] == -1:
        return flask.redirect(dest_url)
    else:
        # form submission
        args = tf[2]
        # modifie le codage des dates
        # (nb: ce formulaire ne permet de créer que des évaluation sur la même journée)
        if args.get("jour"):
            try:
                date_debut = datetime.datetime.strptime(args["jour"], "%d/%m/%Y")
            except ValueError as exc:
                raise ScoValueError("Date (j/m/a) invalide") from exc
        else:
            date_debut = None
        args.pop("jour", None)
        if date_debut and args.get("heure_debut"):
            try:
                heure_debut = heure_to_time(args["heure_debut"])
            except ValueError as exc:
                raise ScoValueError("Heure début invalide") from exc
            args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
        args.pop("heure_debut", None)
        # note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour.
        if date_debut and args.get("heure_fin"):
            try:
                heure_fin = heure_to_time(args["heure_fin"])
            except ValueError as exc:
                raise ScoValueError("Heure fin invalide") from exc
            args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
        args.pop("heure_fin", None)
        #
        if edit:
            evaluation.from_dict(args)
        else:
            # création d'une evaluation
            evaluation = Evaluation.create(moduleimpl=modimpl, **args)
            db.session.add(evaluation)
            db.session.commit()
            evaluation_id = evaluation.id
        if is_apc:
            # Set poids
            evaluation = db.session.get(Evaluation, evaluation_id)
            for ue in sem_ues:
                evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"])
        db.session.add(evaluation)
        db.session.commit()
        sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id)
        return flask.redirect(dest_url)