##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################

"""Jury BUT: affichage/formulaire
"""

import re
import numpy as np

import flask
from flask import flash, render_template, url_for
from flask import g, request

from app import db
from app.but import jury_but
from app.but.jury_but import (
    DecisionsProposeesAnnee,
    DecisionsProposeesRCUE,
    DecisionsProposeesUE,
)
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.models import (
    ApcNiveau,
    FormSemestre,
    FormSemestreInscription,
    Identite,
    UniteEns,
    ScolarAutorisationInscription,
    ScolarFormSemestreValidation,
)
from app.models.config import ScoDocSiteConfig
from app.scodoc import html_sco_header
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_preferences
from app.scodoc import sco_utils as scu


def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
    """Affichage des décisions annuelles BUT
    Si pas read_only, menus sélection codes jury.
    """
    H = []

    if deca.jury_annuel:
        H.append(
            f"""
        <div class="but_section_annee">
            <div>
                <b>Décision de jury pour l'année :</b> {
                    _gen_but_select("code_annee", deca.codes, deca.code_valide,
                        disabled=True, klass="manual")
                }
                <span>({deca.code_valide or 'non'} enregistrée)</span>
            </div>
        </div>
        """
        )

    formsemestre_1 = deca.formsemestre_impair
    formsemestre_2 = deca.formsemestre_pair
    # Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval):
    reverse_semestre = (
        deca.formsemestre_pair
        and deca.formsemestre_impair
        and deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut
    )
    if reverse_semestre:
        formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1
    H.append(
        f"""
    <div class="titre_niveaux">
        <b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
    </div>
    <div class="but_explanation">{deca.explanation}</div>
    <div class="but_annee">
    <div class="titre"></div>
    <div class="titre">{"S" +str(formsemestre_1.semestre_id)
        if formsemestre_1 else "-"}
        <span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str()
            if formsemestre_1 else ""}</span>
    </div>
    <div class="titre">{"S"+str(formsemestre_2.semestre_id)
        if formsemestre_2 else "-"}
        <span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str()
            if formsemestre_2 else ""}</span>
    </div>
    <div class="titre">RCUE</div>
    """
    )
    for niveau in deca.niveaux_competences:
        H.append(
            f"""<div class="but_niveau_titre">
            <div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
            </div>"""
        )
        dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)  # peut être None
        ues = [
            ue
            for ue in deca.ues_impair
            if ue.niveau_competence and ue.niveau_competence.id == niveau.id
        ]
        ue_impair = ues[0] if ues else None
        ues = [
            ue
            for ue in deca.ues_pair
            if ue.niveau_competence and ue.niveau_competence.id == niveau.id
        ]
        ue_pair = ues[0] if ues else None
        # Les UEs à afficher,
        #  qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
        ues_ro = [
            (
                ue_impair,
                (deca.a_cheval and deca.formsemestre_id != deca.formsemestre_impair.id),
            ),
            (
                ue_pair,
                deca.a_cheval and deca.formsemestre_id != deca.formsemestre_pair.id,
            ),
        ]
        # Ordonne selon les dates des 2 semestres considérés:
        if reverse_semestre:
            ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0]
        # Colonnes d'UE:
        for ue, ue_read_only in ues_ro:
            if ue:
                H.append(
                    _gen_but_niveau_ue(
                        ue,
                        deca.decisions_ues[ue.id],
                        disabled=read_only or ue_read_only,
                        annee_prec=ue_read_only,
                        niveau_id=ue.niveau_competence.id,
                    )
                )
            else:
                H.append("""<div class="niveau_vide"></div>""")

        # Colonne RCUE
        H.append(_gen_but_rcue(dec_rcue, niveau))

    H.append("</div>")  # but_annee
    return "\n".join(H)


def _gen_but_select(
    name: str,
    codes: list[str],
    code_valide: str,
    disabled: bool = False,
    klass: str = "",
    data: dict = {},
) -> str:
    "Le menu html select avec les codes"
    # if disabled:  # mauvaise idée car le disabled est traité en JS
    #    return f"""<div class="but_code {klass}">{code_valide}</div>"""
    options_htm = "\n".join(
        [
            f"""<option value="{code}"
            {'selected' if code == code_valide else ''}
            class="{'recorded' if code == code_valide else ''}"
            >{code}</option>"""
            for code in codes
        ]
    )
    return f"""<select required name="{name}"
        class="but_code {klass}" 
        data-orig_code="{code_valide or (codes[0] if codes else '')}"
        data-orig_recorded="{code_valide or ''}"
        onchange="change_menu_code(this);"
        {"disabled" if disabled else ""}
        {" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
        >{options_htm}</select>
        """


def _gen_but_niveau_ue(
    ue: UniteEns,
    dec_ue: DecisionsProposeesUE,
    disabled: bool = False,
    annee_prec: bool = False,
    niveau_id: int = None,
) -> str:
    if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
        moy_ue_str = f"""<span class="ue_cap">{
            scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
        scoplement = f"""<div class="scoplement">
            <div>
            <b>UE {ue.acronyme} capitalisée </b>
            <span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
            </span>
            </div>
            <div>UE en cours
            {   "sans notes" if np.isnan(dec_ue.moy_ue)
                else
                ("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>") 
            }
            </div>
        </div>
        """
    else:
        moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
        if dec_ue.code_valide:
            scoplement = f"""<div class="scoplement">
            <div>Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
            à {dec_ue.validation.event_date.strftime("%Hh%M")}
            </div>
            </div>
            """
        else:
            scoplement = ""

    return f"""<div class="but_niveau_ue {
        'recorded' if dec_ue.code_valide is not None else ''}
        {'annee_prec' if annee_prec else ''}
        ">
    <div title="{ue.titre}">{ue.acronyme}</div>
    <div class="but_note with_scoplement">
        <div>{moy_ue_str}</div>
        {scoplement}
    </div>
    <div class="but_code">{
        _gen_but_select("code_ue_"+str(ue.id), 
            dec_ue.codes,
            dec_ue.code_valide,
            disabled=disabled,
            klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else ""
        )
    }</div>
    
    </div>"""


def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
    if dec_rcue is None:
        return """
        <div class="but_niveau_rcue niveau_vide with_scoplement">
                <div></div>
                <div class="scoplement">Pas de RCUE (UE non capitalisée ?)</div>
        </div>
        """

    scoplement = (
        f"""<div class="scoplement">{
            dec_rcue.validation.to_html()
        }</div>"""
        if dec_rcue.validation
        else ""
    )

    # Déjà enregistré ?
    niveau_rcue_class = ""
    if dec_rcue.code_valide is not None and dec_rcue.codes:
        if dec_rcue.code_valide == dec_rcue.codes[0]:
            niveau_rcue_class = "recorded"
        else:
            niveau_rcue_class = "recorded_different"

    return f"""
        <div class="but_niveau_rcue {niveau_rcue_class}
        ">
            <div class="but_note with_scoplement">
                <div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
                {scoplement}
            </div>
            <div class="but_code">
                {_gen_but_select("code_rcue_"+str(niveau.id),
                    dec_rcue.codes,
                    dec_rcue.code_valide,
                    disabled=True,
                    klass="manual code_rcue",
                    data = { "niveau_id" : str(niveau.id)}
                )}
            </div>
        </div>
    """


def jury_but_semestriel(
    formsemestre: FormSemestre,
    etud: Identite,
    read_only: bool,
    navigation_div: str = "",
) -> str:
    """Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)."""
    res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
    parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
    inscription_etat = etud.inscription_etat(formsemestre.id)
    semestre_terminal = (
        formsemestre.semestre_id >= formsemestre.formation.get_cursus().NB_SEM
    )
    autorisations_passage = ScolarAutorisationInscription.query.filter_by(
        etudid=etud.id,
        origin_formsemestre_id=formsemestre.id,
    ).all()
    # Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
    # ou si décision déjà enregistrée:
    est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
        formsemestre.semestre_id + 1
    ) in (a.semestre_id for a in autorisations_passage)
    decisions_ues = {
        ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
        for ue in ues
    }
    for dec_ue in decisions_ues.values():
        dec_ue.compute_codes()

    if request.method == "POST":
        if not read_only:
            for key in request.form:
                code = request.form[key]
                # Codes d'UE
                code_match = re.match(r"^code_ue_(\d+)$", key)
                if code_match:
                    ue_id = int(code_match.group(1))
                    dec_ue = decisions_ues.get(ue_id)
                    if not dec_ue:
                        raise ScoValueError(f"UE invalide ue_id={ue_id}")
                    dec_ue.record(code)
                    db.session.commit()
            flash("codes enregistrés")
            if not semestre_terminal:
                if request.form.get("autorisation_passage"):
                    if not formsemestre.semestre_id + 1 in (
                        a.semestre_id for a in autorisations_passage
                    ):
                        ScolarAutorisationInscription.delete_autorisation_etud(
                            etud.id, formsemestre.id
                        )
                        ScolarAutorisationInscription.autorise_etud(
                            etud.id,
                            formsemestre.formation.formation_code,
                            formsemestre.id,
                            formsemestre.semestre_id + 1,
                        )
                        db.session.commit()
                        flash(
                            f"""autorisation de passage en S{formsemestre.semestre_id + 1
                            } enregistrée"""
                        )
                else:
                    if est_autorise_a_passer:
                        ScolarAutorisationInscription.delete_autorisation_etud(
                            etud.id, formsemestre.id
                        )
                        db.session.commit()
                        flash(
                            f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée"
                        )
        return flask.redirect(
            url_for(
                "notes.formsemestre_validation_but",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre.id,
                etudid=etud.id,
            )
        )
    # GET
    if formsemestre.semestre_id % 2 == 0:
        warning = f"""<div class="warning">
        Cet étudiant de S{formsemestre.semestre_id} ne peut pas passer 
        en jury BUT annuel car il lui manque le semestre précédent.
        </div>"""
    else:
        warning = ""
    H = [
        html_sco_header.sco_header(
            page_title=f"Validation BUT S{formsemestre.semestre_id}",
            formsemestre_id=formsemestre.id,
            etudid=etud.id,
            cssstyles=("css/jury_but.css",),
            javascripts=("js/jury_but.js",),
        ),
        f"""
        <div class="jury_but">
        <div>
            <div class="bull_head">
                <div>
                    <div class="titre_parcours">Jury BUT S{formsemestre.id}
                    - Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
                    </div>
                    <div class="nom_etud">{etud.nomprenom}</div>
                </div>
                <div class="bull_photo"><a href="{
                    url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
                    }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
                </div>
            </div>
            <h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
            {warning}
        </div>

        <form method="post" id="jury_but">
        """,
    ]

    erase_span = ""
    if not read_only:
        # Requête toutes les validations (pas seulement celles du deca courant),
        # au cas où: changement d'architecture, saisie en mode classique, ...
        validations = ScolarFormSemestreValidation.query.filter_by(
            etudid=etud.id, formsemestre_id=formsemestre.id
        ).all()
        if validations:
            erase_span = f"""<a href="{
                url_for("notes.formsemestre_jury_but_erase",
                scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
                etudid=etud.id, only_one_sem=1)
                }" class="stdlink">effacer les décisions enregistrées</a>"""
        else:
            erase_span = (
                "Cet étudiant n'a aucune décision enregistrée pour ce semestre."
            )

    H.append(
        f"""
    <div class="but_section_annee">
    </div>
    <div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
    """
    )
    if not ues:
        H.append(
            """<div class="warning">Aucune UE ! Vérifiez votre programme de
        formation, et l'association UEs / Niveaux de compétences</div>"""
        )
    else:
        H.append(
            """
            <div class="but_annee">
            <div class="titre"></div>
            <div class="titre"></div>
            <div class="titre"></div>
            <div class="titre"></div>
            """
        )
        for ue in ues:
            dec_ue = decisions_ues[ue.id]
            H.append("""<div class="but_niveau_titre"><div></div></div>""")
            H.append(
                _gen_but_niveau_ue(
                    ue,
                    dec_ue,
                    disabled=read_only,
                )
            )
            H.append(
                """<div style=""></div>
            <div class=""></div>"""
            )
        H.append("</div>")  # but_annee

    div_autorisations_passage = (
        f"""
        <div class="but_autorisations_passage">
            <span>Autorisé à passer en&nbsp;:</span>
            { ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
        </div>
    """
        if autorisations_passage
        else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
    )
    H.append(div_autorisations_passage)

    if read_only:
        H.append(
            f"""<div class="but_explanation">
            {"Vous n'avez pas la permission de modifier ces décisions."
            if formsemestre.etat
            else "Semestre verrouillé."}
            Les champs entourés en vert sont enregistrés.
            </div>
            """
        )
    else:
        if formsemestre.semestre_id < formsemestre.formation.get_cursus().NB_SEM:
            H.append(
                f"""
                <div class="but_settings">
                    <input type="checkbox" name="autorisation_passage" value="1" {
                        "checked" if est_autorise_a_passer else ""}>
                        <em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
                    </input>
                </div>
                """
            )
        else:
            H.append("""<div class="help">dernier semestre de la formation.</div>""")
        H.append(
            f"""
            <div class="but_buttons">
                <span><input type="submit" value="Enregistrer ces décisions"></span>
                <span>{erase_span}</span>
            </div>
            """
        )

    H.append(navigation_div)
    H.append("</div>")
    H.append(
        render_template(
            "but/documentation_codes_jury.j2",
            nom_univ=f"""Export {sco_preferences.get_preference("InstituteName")
            or sco_preferences.get_preference("UnivName")
            or "Apogée"}""",
            codes=ScoDocSiteConfig.get_codes_apo_dict(),
        )
    )

    return "\n".join(H)


# -------------
def infos_fiche_etud_html(etudid: int) -> str:
    """Section html pour fiche etudiant
    provisoire pour BUT 2022
    """
    etud: Identite = Identite.query.get_or_404(etudid)
    inscriptions = (
        FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
        .filter(
            FormSemestreInscription.etudid == etud.id,
        )
        .order_by(FormSemestre.date_debut)
    )
    formsemestres_but = [
        i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc()
    ]
    if len(formsemestres_but) == 0:
        return ""

    # temporaire quick & dirty: affiche le dernier
    try:
        deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
        return f"""<div class="infos_but">
            {show_etud(deca, read_only=True)}
            </div>
        """
    except ScoValueError:
        pass

    return ""