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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 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@viennet.net
#
##############################################################################

"""Génération du bulletin en format JSON

"""
import datetime
import json

from flask import abort

from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import but_validations
from app.models import Matiere, ModuleImpl, UniteEns
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre

import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_photos
from app.scodoc import sco_preferences
from app.scodoc import sco_etud
from app.scodoc.sco_preferences import SemPreferences
from app.scodoc.sco_xml import quote_xml_attr

# -------- Bulletin en JSON


def make_json_formsemestre_bulletinetud(
    formsemestre_id: int,
    etudid: int,
    xml_with_decisions=False,
    version="long",
    force_publishing=False,  # force publication meme si semestre non publie sur "portail"
) -> str:
    """Renvoie bulletin en chaine JSON"""

    d = formsemestre_bulletinetud_published_dict(
        formsemestre_id,
        etudid,
        force_publishing=force_publishing,
        xml_with_decisions=xml_with_decisions,
        version=version,
    )

    return json.dumps(d, cls=scu.ScoDocJSONEncoder)


# (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
#   pour simplifier le code, mais attention a la maintenance !)
#
def formsemestre_bulletinetud_published_dict(
    formsemestre_id,
    etudid,
    force_publishing=False,
    xml_nodate=False,
    xml_with_decisions=False,  # inclue les decisions même si non publiées
    version="long",
) -> dict:
    """Dictionnaire representant les informations _publiees_ du bulletin de notes
    Utilisé pour JSON, devrait l'être aussi pour XML. (todo)

    version:
        short (sans les évaluations)
        long  (avec les évaluations)

        short_mat (sans évaluations, et structuration en matières)
        long_mat  (avec évaluations, et structuration en matières)
    """
    from app.scodoc import sco_bulletins

    with_matieres = False
    if version.endswith("_mat"):
        version = version[:-4]  # enlève le "_mat"
        with_matieres = True

    formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
    prefs = sco_preferences.SemPreferences(formsemestre_id)
    etud = Identite.query.get(etudid)
    sem = sco_formsemestre.get_formsemestre(formsemestre_id)

    nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
    if not etudid in nt.identdict:
        abort(404, "etudiant non inscrit dans ce semestre")
    d = {"type": "classic", "version": "0"}
    if (not sem["bul_hide_xml"]) or force_publishing:
        published = True
    else:
        published = False
    if xml_nodate:
        docdate = ""
    else:
        docdate = datetime.datetime.now().isoformat()

    el = {
        "etudid": etudid,
        "formsemestre_id": formsemestre_id,
        "date": docdate,
        "publie": published,
        "etapes": sem["etapes"],
    }
    # backward compat:
    if sem["etapes"]:
        el["etape_apo"] = sem["etapes"][0] or ""
        n = 2
        for et in sem["etapes"][1:]:
            el["etape_apo" + str(n)] = et or ""
            n += 1
    d.update(**el)

    # Infos sur l'etudiant
    etudinfo = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]

    d["etudiant"] = dict(
        etudid=etudid,
        code_nip=etudinfo["code_nip"],
        code_ine=etudinfo["code_ine"],
        nom=quote_xml_attr(etudinfo["nom"]),
        prenom=quote_xml_attr(etudinfo["prenom"]),
        civilite=quote_xml_attr(etudinfo["civilite_str"]),
        photo_url=quote_xml_attr(sco_photos.etud_photo_url(etudinfo, fast=True)),
        email=quote_xml_attr(etudinfo["email"]),
        emailperso=quote_xml_attr(etudinfo["emailperso"]),
    )
    d["etudiant"]["sexe"] = d["etudiant"]["civilite"]  # backward compat for our clients
    # Disponible pour publication ?
    d["publie"] = published
    if not published:
        return d  # stop !

    etat_inscription = etud.inscription_etat(formsemestre.id)
    if etat_inscription != scu.INSCRIT:
        d.update(dict_decision_jury(etud, formsemestre, with_decisions=True))
        return d

    # Groupes:
    partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
    partitions_etud_groups = {}  # { partition_id : { etudid : group } }
    for partition in partitions:
        pid = partition["partition_id"]
        partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)

    ues_stat = nt.get_ues_stat_dict()
    modimpls = nt.get_modimpls_dict()
    nbetuds = len(nt.etud_moy_gen_ranks)
    moy_gen = scu.fmt_note(nt.get_etud_moy_gen(etudid))
    if nt.get_moduleimpls_attente() or not prefs["bul_show_rangs"]:
        # n'affiche pas le rang sur le bulletin s'il y a des
        # notes en attente dans ce semestre
        rang = ""
        rang_gr = {}
        ninscrits_gr = {}
    else:
        rang = str(nt.get_etud_rang(etudid))
        rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups(
            etudid, partitions, partitions_etud_groups, nt
        )

    d["note"] = dict(
        value=moy_gen,
        min=scu.fmt_note(nt.moy_min),
        max=scu.fmt_note(nt.moy_max),
        moy=scu.fmt_note(nt.moy_moy),
    )
    d["rang"] = dict(value=rang, ninscrits=nbetuds)
    d["rang_group"] = []
    if rang_gr:
        for partition in partitions:
            d["rang_group"].append(
                dict(
                    group_type=partition["partition_name"],
                    group_name=gr_name[partition["partition_id"]],
                    value=rang_gr[partition["partition_id"]],
                    ninscrits=ninscrits_gr[partition["partition_id"]],
                )
            )

    d["note_max"] = dict(value=20)  # notes toujours sur 20
    d["bonus_sport_culture"] = dict(
        value=nt.bonus[etudid] if nt.bonus is not None else 0.0
    )

    # Liste les UE / modules /evals
    d["ue"] = []
    d["ue_capitalisee"] = []
    for ue_st in ues_stat:
        ue_id = ue_st["ue_id"]
        ue_status = nt.get_etud_ue_status(etudid, ue_id)
        if ue_st["ects"] is None:
            ects_txt = ""
        else:
            ects_txt = f"{ue_st['ects']:2.3g}"
        rang, effectif = nt.get_etud_ue_rang(ue_id, etudid)
        u = dict(
            id=ue_id,
            numero=quote_xml_attr(ue_st["numero"]),
            acronyme=quote_xml_attr(ue_st["acronyme"]),
            titre=quote_xml_attr(ue_st["titre"]),
            note=dict(
                value=scu.fmt_note(ue_status["cur_moy_ue"] if ue_status else ""),
                min=scu.fmt_note(ue_st["min"]),
                max=scu.fmt_note(ue_st["max"]),
                moy=scu.fmt_note(ue_st["moy"]),
            ),
            rang=rang,
            effectif=effectif,
            ects=ects_txt,
            code_apogee=quote_xml_attr(ue_st["code_apogee"]),
        )
        d["ue"].append(u)

        if with_matieres:
            u["module"] = []
            # Structure UE/Matière/Module
            # Recodé en 2022
            ue = UniteEns.query.get(ue_id)
            u["matiere"] = [
                {
                    "matiere_id": mat.id,
                    "note": scu.fmt_note(nt.get_etud_mat_moy(mat.id, etudid)),
                    "titre": mat.titre,
                    "module": _list_modimpls(
                        nt,
                        etudid,
                        [
                            mod
                            for mod in modimpls
                            if mod["module"]["matiere_id"] == mat.id
                        ],
                        prefs,
                        version,
                    ),
                }
                for mat in ue.matieres.order_by(Matiere.numero)
            ]

        else:
            # Liste les modules de l'UE
            u["module"] = _list_modimpls(
                nt,
                etudid,
                [mod for mod in modimpls if mod["module"]["ue_id"] == ue_id],
                prefs,
                version,
            )

        # UE capitalisée (listée seulement si meilleure que l'UE courante)
        if ue_status["is_capitalized"]:
            try:
                ects_txt = str(int(ue_status["ue"].get("ects", "") or 0.0))
            except ValueError:
                ects_txt = ""
            d["ue_capitalisee"].append(
                dict(
                    id=ue_id,
                    numero=quote_xml_attr(ue_st["numero"]),
                    acronyme=quote_xml_attr(ue_st["acronyme"]),
                    titre=quote_xml_attr(ue_st["titre"]),
                    note=scu.fmt_note(ue_status["moy"]),
                    coefficient_ue=scu.fmt_note(ue_status["coef_ue"]),
                    date_capitalisation=ndb.DateDMYtoISO(ue_status["event_date"]),
                    ects=ects_txt,
                )
            )

    # --- Absences
    if prefs["bul_show_abs"]:
        nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
        d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)

    # --- Décision Jury
    d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions))

    # --- Appréciations
    cnx = ndb.GetDBConnexion()
    apprecs = sco_etud.appreciations_list(
        cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
    )
    d["appreciation"] = []
    for app in apprecs:
        d["appreciation"].append(
            dict(
                comment=quote_xml_attr(app["comment"]),
                date=ndb.DateDMYtoISO(app["date"]),
            )
        )

    #
    return d


def _list_modimpls(
    nt: NotesTableCompat,
    etudid: int,
    modimpls: list[ModuleImpl],
    prefs: SemPreferences,
    version: str,
) -> list[dict]:
    modules_dict = []
    for modimpl in modimpls:
        mod_moy = scu.fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
        if mod_moy == "NI":  # ne mentionne pas les modules ou n'est pas inscrit
            continue
        mod = modimpl["module"]
        # if mod['ects'] is None:
        #    ects = ''
        # else:
        #    ects = str(mod['ects'])
        modstat = nt.get_mod_stats(modimpl["moduleimpl_id"])

        mod_dict = dict(
            id=modimpl["moduleimpl_id"],
            code=mod["code"],
            coefficient=mod["coefficient"],
            numero=mod["numero"],
            titre=quote_xml_attr(mod["titre"]),
            abbrev=quote_xml_attr(mod["abbrev"]),
            # ects=ects, ects des modules maintenant inutilisés
            note=dict(value=mod_moy),
            code_apogee=quote_xml_attr(mod["code_apogee"]),
            matiere_id=mod["matiere_id"],
        )
        mod_dict["note"].update(modstat)
        for k in ("min", "max", "moy"):  # formatte toutes les notes
            mod_dict["note"][k] = scu.fmt_note(mod_dict["note"][k])

        if prefs["bul_show_mod_rangs"] and nt.mod_rangs is not None:
            mod_dict["rang"] = dict(
                value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid]
            )
            mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1])

        # --- notes de chaque eval:
        evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
        mod_dict["evaluation"] = []
        if version != "short":
            for e in evals:
                if e["visibulletin"] or version == "long":
                    val = e["notes"].get(etudid, {"value": "NP"})["value"]
                    # nb: val est NA si etud démissionnaire
                    val = scu.fmt_note(val, note_max=e["note_max"])
                    eval_dict = dict(
                        jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
                        heure_debut=ndb.TimetoISO8601(
                            e["heure_debut"], null_is_empty=True
                        ),
                        heure_fin=ndb.TimetoISO8601(e["heure_fin"], null_is_empty=True),
                        coefficient=e["coefficient"],
                        evaluation_type=e["evaluation_type"],
                        # CM : ajout pour permettre de faire le lien sur
                        # les bulletins en ligne avec l'évaluation:
                        evaluation_id=e["evaluation_id"],
                        description=quote_xml_attr(e["description"]),
                        note=val,
                    )
                    if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
                        etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
                        if prefs["bul_show_minmax_eval"]:
                            eval_dict["min"] = scu.fmt_note(etat["mini"])
                            eval_dict["max"] = scu.fmt_note(etat["maxi"])
                        if prefs["bul_show_moypromo"]:
                            eval_dict["moy"] = scu.fmt_note(etat["moy"])

                    mod_dict["evaluation"].append(eval_dict)

            # Evaluations incomplètes ou futures:
            complete_eval_ids = set([e["evaluation_id"] for e in evals])
            if prefs["bul_show_all_evals"]:
                all_evals = sco_evaluation_db.do_evaluation_list(
                    args={"moduleimpl_id": modimpl["moduleimpl_id"]}
                )
                all_evals.reverse()  # plus ancienne d'abord
                for e in all_evals:
                    if e["evaluation_id"] not in complete_eval_ids:
                        mod_dict["evaluation"].append(
                            dict(
                                jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
                                heure_debut=ndb.TimetoISO8601(
                                    e["heure_debut"], null_is_empty=True
                                ),
                                heure_fin=ndb.TimetoISO8601(
                                    e["heure_fin"], null_is_empty=True
                                ),
                                coefficient=e["coefficient"],
                                description=quote_xml_attr(e["description"]),
                                incomplete="1",
                            )
                        )
        modules_dict.append(mod_dict)
    return modules_dict


def dict_decision_jury(
    etud: Identite, formsemestre: FormSemestre, with_decisions: bool = False
) -> dict:
    """dict avec decision pour bulletins json
    - decision : décision semestre
    - decision_ue : list des décisions UE
    - situation

    with_decision donne les décision même si bul_show_decision est faux.

    Si formation APC, indique aussi validations année et RCUEs

    Exemple:
    {
        'autorisation_inscription': [{'semestre_id': 4}],
        'decision': {'code': 'ADM',
                    'compense_formsemestre_id': None,
                    'date': '2022-01-21',
                    'etat': 'I'},
        'decision_ue': [
            {
            'acronyme': 'UE31',
            'code': 'ADM',
            'ects': 16.0,
            'numero': 23,
            'titre': 'Approfondissement métiers',
            'ue_id': 1787
            },
            ...
        ],
        'situation': 'Inscrit le 25/06/2021. Décision jury: Validé.  UE acquises: '
                    'UE31, UE32.  Diplôme obtenu.'}
    """
    from app.scodoc import sco_bulletins

    prefs = sco_preferences.SemPreferences(formsemestre.id)

    d = {}
    if prefs["bul_show_decision"] or with_decisions:
        infos, dpv = sco_bulletins.etud_descr_situation_semestre(
            etud.id,
            formsemestre,
            show_uevalid=prefs["bul_show_uevalid"],
        )
        d["situation"] = infos["situation"]
        if dpv:
            decision = dpv["decisions"][0]
            etat = decision["etat"]
            if decision["decision_sem"]:
                code = decision["decision_sem"]["code"]
                date = ndb.DateDMYtoISO(
                    dpv["decisions"][0]["decision_sem"]["event_date"]
                )
            else:
                code = ""
                date = ""

            d["decision"] = dict(
                code=code,
                etat=etat,
                date=date,
            )
            if (
                decision["decision_sem"]
                and "compense_formsemestre_id" in decision["decision_sem"]
            ):
                d["decision"]["compense_formsemestre_id"] = decision["decision_sem"][
                    "compense_formsemestre_id"
                ]

            d["decision_ue"] = []
            if decision[
                "decisions_ue"
            ]:  # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
                for ue_id in decision["decisions_ue"].keys():
                    ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
                    d["decision_ue"].append(
                        dict(
                            ue_id=ue["ue_id"],
                            numero=ue["numero"],
                            acronyme=ue["acronyme"],
                            titre=ue["titre"],
                            code=decision["decisions_ue"][ue_id]["code"],
                            ects=ue["ects"] or "",
                        )
                    )
            d["autorisation_inscription"] = []
            for aut in decision["autorisations"]:
                d["autorisation_inscription"].append(
                    dict(semestre_id=aut["semestre_id"])
                )
        else:
            d["decision"] = dict(code="", etat="DEM")
        # Ajout jury BUT:
        if formsemestre.formation.is_apc():
            d.update(but_validations.dict_decision_jury(etud, formsemestre))
    return d