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

"""Génération bulletin BUT
"""

import datetime
from flask import url_for, g
from app.models.formsemestre import FormSemestre

from app.scodoc import sco_utils as scu
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_preferences
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import fmt_note
from app.comp.res_but import ResultatsSemestreBUT


class BulletinBUT:
    """Génération du bulletin BUT.
    Cette classe génère des dictionnaires avec toutes les informations
    du bulletin, qui sont immédiatement traduisibles en JSON.
    """

    def __init__(self, formsemestre: FormSemestre):
        """ """
        self.res = ResultatsSemestreBUT(formsemestre)

    def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
        "dict synthèse résultats dans l'UE pour les modules indiqués"
        res = self.res
        d = {}
        etud_idx = res.etud_index[etud.id]
        if ue.type != UE_SPORT:
            ue_idx = res.modimpl_coefs_df.index.get_loc(ue.id)
        etud_moy_module = res.sem_cube[etud_idx]  # module x UE
        for modimpl in modimpls:
            if res.modimpl_inscr_df[modimpl.id][etud.id]:  # si inscrit
                if ue.type != UE_SPORT:
                    coef = res.modimpl_coefs_df[modimpl.id][ue.id]
                    if coef > 0:
                        d[modimpl.module.code] = {
                            "id": modimpl.id,
                            "coef": coef,
                            "moyenne": fmt_note(
                                etud_moy_module[
                                    res.modimpl_coefs_df.columns.get_loc(modimpl.id)
                                ][ue_idx]
                            ),
                        }
                # else:  # modules dans UE bonus sport
                #     d[modimpl.module.code] = {
                #         "id": modimpl.id,
                #         "coef": "",
                #         "moyenne": "?x?",
                #     }
        return d

    def etud_ue_results(self, etud, ue):
        "dict synthèse résultats UE"
        res = self.res
        d = {
            "id": ue.id,
            "titre": ue.titre,
            "numero": ue.numero,
            "type": ue.type,
            "ECTS": {
                "acquis": 0,  # XXX TODO voir jury #sco92
                "total": ue.ects,
            },
            "color": ue.color,
            "competence": None,  # XXX TODO lien avec référentiel
            "moyenne": None,
            # Le bonus sport appliqué sur cette UE
            "bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
            if res.bonus_ues is not None and ue.id in res.bonus_ues
            else fmt_note(0.0),
            "malus": res.malus[ue.id][etud.id],
            "capitalise": None,  # "AAAA-MM-JJ" TODO #sco92
            "ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
            "saes": self.etud_ue_mod_results(etud, ue, res.saes),
        }
        if ue.type != UE_SPORT:
            if sco_preferences.get_preference("bul_show_ue_rangs", res.formsemestre.id):
                rangs, effectif = res.ue_rangs[ue.id]
                rang = rangs[etud.id]
            else:
                rang, effectif = "", 0
            d["moyenne"] = {
                "value": fmt_note(res.etud_moy_ue[ue.id][etud.id]),
                "min": fmt_note(res.etud_moy_ue[ue.id].min()),
                "max": fmt_note(res.etud_moy_ue[ue.id].max()),
                "moy": fmt_note(res.etud_moy_ue[ue.id].mean()),
                "rang": rang,
                "total": effectif,  # nb etud avec note dans cette UE
            }
        else:
            # ceci suppose que l'on a une seule UE bonus,
            # en tous cas elles auront la même description
            d["bonus_description"] = self.etud_bonus_description(etud.id)
            modimpls_spo = [
                modimpl
                for modimpl in res.formsemestre.modimpls_sorted
                if modimpl.module.ue.type == UE_SPORT
            ]
            d["modules"] = self.etud_mods_results(etud, modimpls_spo)
        return d

    def etud_mods_results(self, etud, modimpls) -> dict:
        """dict synthèse résultats des modules indiqués,
        avec évaluations de chacun."""
        res = self.res
        d = {}
        # etud_idx = self.etud_index[etud.id]
        for modimpl in modimpls:
            # mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
            # # moyennes indicatives (moyennes de moyennes d'UE)
            # try:
            #     moyennes_etuds = np.nan_to_num(
            #         np.nanmean(self.sem_cube[:, mod_idx, :], axis=1),
            #         copy=False,
            #     )
            # except RuntimeWarning:
            # # all nans in np.nanmean (sur certains etuds sans notes valides)
            #     pass
            # try:
            #     moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
            # except RuntimeWarning:  # all nans in np.nanmean
            #     pass
            modimpl_results = res.modimpls_results[modimpl.id]
            if res.modimpl_inscr_df[modimpl.id][etud.id]:  # si inscrit
                d[modimpl.module.code] = {
                    "id": modimpl.id,
                    "titre": modimpl.module.titre,
                    "code_apogee": modimpl.module.code_apogee,
                    "url": url_for(
                        "notes.moduleimpl_status",
                        scodoc_dept=g.scodoc_dept,
                        moduleimpl_id=modimpl.id,
                    ),
                    "moyenne": {
                        # # moyenne indicative de module: moyenne des UE,
                        # # ignorant celles sans notes (nan)
                        # "value": fmt_note(moy_indicative_mod),
                        # "min": fmt_note(moyennes_etuds.min()),
                        # "max": fmt_note(moyennes_etuds.max()),
                        # "moy": fmt_note(moyennes_etuds.mean()),
                    },
                    "evaluations": [
                        self.etud_eval_results(etud, e)
                        for e in modimpl.evaluations
                        if e.visibulletin
                        and (
                            modimpl_results.evaluations_etat[e.id].is_complete
                            or sco_preferences.get_preference(
                                "bul_show_all_evals", res.formsemestre.id
                            )
                        )
                    ],
                }
        return d

    def etud_eval_results(self, etud, e) -> dict:
        "dict resultats d'un étudiant à une évaluation"
        # eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
        eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
        notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
        d = {
            "id": e.id,
            "description": e.description,
            "date": e.jour.isoformat() if e.jour else None,
            "heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
            "heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
            "coef": e.coefficient,
            "poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
            "note": {
                "value": fmt_note(
                    eval_notes[etud.id],
                    note_max=e.note_max,
                ),
                "min": fmt_note(notes_ok.min()),
                "max": fmt_note(notes_ok.max()),
                "moy": fmt_note(notes_ok.mean()),
            },
            "url": url_for(
                "notes.evaluation_listenotes",
                scodoc_dept=g.scodoc_dept,
                evaluation_id=e.id,
            ),
        }
        return d

    def etud_bonus_description(self, etudid):
        """description du bonus affichée dans la section "UE bonus"."""
        res = self.res
        if res.bonus_ues is None or res.bonus_ues.shape[1] == 0:
            return ""

        bonus_vect = res.bonus_ues.loc[etudid]
        if bonus_vect.nunique() > 1:
            # détail UE par UE
            details = [
                f"{fmt_note(bonus_vect[ue.id])} sur {ue.acronyme}"
                for ue in res.ues
                if res.modimpls_in_ue(ue.id, etudid)
                and ue.id in res.bonus_ues
                and bonus_vect[ue.id] > 0.0
            ]
            if details:
                return "Bonus de " + ", ".join(details)
            else:
                return ""  # aucun bonus
        else:
            return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"

    def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict:
        """Le bulletin de l'étudiant dans ce semestre.
        Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
        (bulletins non publiés).
        """
        res = self.res
        etat_inscription = etud.etat_inscription(formsemestre.id)
        nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
        published = (not formsemestre.bul_hide_xml) or force_publishing
        d = {
            "version": "0",
            "type": "BUT",
            "date": datetime.datetime.utcnow().isoformat() + "Z",
            "publie": not formsemestre.bul_hide_xml,
            "etudiant": etud.to_dict_bul(),
            "formation": {
                "id": formsemestre.formation.id,
                "acronyme": formsemestre.formation.acronyme,
                "titre_officiel": formsemestre.formation.titre_officiel,
                "titre": formsemestre.formation.titre,
            },
            "formsemestre_id": formsemestre.id,
            "etat_inscription": etat_inscription,
            "options": sco_preferences.bulletin_option_affichage(formsemestre.id),
        }
        if not published:
            return d

        nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
        semestre_infos = {
            "etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
            "date_debut": formsemestre.date_debut.isoformat(),
            "date_fin": formsemestre.date_fin.isoformat(),
            "annee_universitaire": formsemestre.annee_scolaire_str(),
            "numero": formsemestre.semestre_id,
            "inscription": "",  # inutilisé mais nécessaire pour le js de Seb.
            "groupes": [],  # XXX TODO
            "absences": {
                "injustifie": nbabsjust,
                "total": nbabs,
            },
        }
        semestre_infos.update(
            sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
        )
        if etat_inscription == scu.INSCRIT:
            semestre_infos.update(
                {
                    "notes": {  # moyenne des moyennes générales du semestre
                        "value": fmt_note(res.etud_moy_gen[etud.id]),
                        "min": fmt_note(res.etud_moy_gen.min()),
                        "moy": fmt_note(res.etud_moy_gen.mean()),
                        "max": fmt_note(res.etud_moy_gen.max()),
                    },
                    "rang": {  # classement wrt moyenne général, indicatif
                        "value": res.etud_moy_gen_ranks[etud.id],
                        "total": nb_inscrits,
                    },
                },
            )
            d.update(
                {
                    "ressources": self.etud_mods_results(etud, res.ressources),
                    "saes": self.etud_mods_results(etud, res.saes),
                    "ues": {
                        ue.acronyme: self.etud_ue_results(etud, ue)
                        for ue in res.ues
                        if self.res.modimpls_in_ue(
                            ue.id, etud.id
                        )  # si l'UE comporte des modules auxquels on est inscrit
                    },
                    "semestre": semestre_infos,
                },
            )
        else:
            semestre_infos.update(
                {
                    "notes": {
                        "value": "DEM",
                        "min": "",
                        "moy": "",
                        "max": "",
                    },
                    "rang": {"value": "DEM", "total": nb_inscrits},
                }
            )
            d.update(
                {
                    "semestre": semestre_infos,
                    "ressources": {},
                    "saes": {},
                    "ues": {},
                }
            )

        return d
