# -*- 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@viennet.net
#
##############################################################################

"""Génération du bulletin en format JSON (formations classiques)

"""
import datetime
import json

from flask import abort

from app import db, ScoDocJSONEncoder
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import but_validations
from app.models import BulAppreciations, Evaluation, Matiere, 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_assiduites
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations
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=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,  # inclure les décisions même si non publiées
    version="long",
) -> dict:
    """Dictionnaire representant les informations _publiees_ du bulletin de notes
    Utilisé pour JSON des formations classiques (mais pas pour le XML, qui est deprecated).

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

        # non implémenté: 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.get_formsemestre(formsemestre_id)
    prefs = sco_preferences.SemPreferences(formsemestre_id)
    etud = Identite.get_etud(etudid)
    sem = sco_formsemestre.get_formsemestre(formsemestre_id)

    nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
    if etudid not in nt.identdict:
        abort(404, "etudiant non inscrit dans ce semestre")
    d = {"type": "classic", "version": "0"}

    published = (not formsemestre.bul_hide_xml) or force_publishing
    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)

    # Il serait préférable de factoriser et d'avoir la même section
    # "semestre" que celle des bulletins BUT.
    etud_groups = sco_groups.get_etud_formsemestre_groups(
        etud, formsemestre, only_to_show=True
    )
    d["semestre"] = {
        "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": [group.to_dict() for group in etud_groups],
    }

    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 = db.session.get(UniteEns, 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"]:
        _, nbabsjust, nbabs = sco_assiduites.get_assiduites_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
    appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid)
    d["appreciation"] = [
        {
            "comment": quote_xml_attr(appreciation.comment),
            "date": appreciation.date.isoformat() if appreciation.date else "",
        }
        for appreciation in appreciations
    ]

    #
    return d


def _list_modimpls(
    nt: NotesTableCompat,
    etudid: int,
    modimpls: list[dict],
    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
        modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
        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:
        evaluations_completes = nt.get_modimpl_evaluations_completes(
            modimpl["moduleimpl_id"]
        )
        mod_dict["evaluation"] = []
        if version != "short":
            for e in evaluations_completes:
                if e.visibulletin or version == "long":
                    # Note à l'évaluation:
                    val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
                    # nb: val est NA si etud démissionnaire
                    e_dict = e.to_dict_bul()
                    e_dict["note"] = scu.fmt_note(val, note_max=e.note_max)

                    if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
                        # XXX à revoir pour utiliser modimplresult
                        etat = sco_evaluations.do_evaluation_etat(e.id)
                        if prefs["bul_show_minmax_eval"]:
                            e_dict["min"] = etat["mini"]  # chaine, sur 20
                            e_dict["max"] = etat["maxi"]
                        if prefs["bul_show_moypromo"]:
                            e_dict["moy"] = etat["moy"]

                    mod_dict["evaluation"].append(e_dict)

            # Evaluations incomplètes ou futures:
            complete_eval_ids = {e.id for e in evaluations_completes}
            if prefs["bul_show_all_evals"]:
                evaluations: list[Evaluation] = Evaluation.query.filter_by(
                    moduleimpl_id=modimpl["moduleimpl_id"]
                ).order_by(Evaluation.date_debut)
                # plus ancienne d'abord
                for e in evaluations:
                    if e.id not in complete_eval_ids:
                        e_dict = e.to_dict_bul()
                        e_dict["incomplete"] = 1
                        mod_dict["evaluation"].append(e_dict)
        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
    - autorisation_inscription
    - decision : décision semestre
    - decision_annee : annee BUT
    - 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.',
        'diplomation' : 'Diplôme obtenu.' # (ou vide)

    }
    """
    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["diplomation"] = infos["diplomation"]
        d["situation"] = infos["situation"]
        d["diplome_dut120"] = infos["diplome_dut120"]
        d["diplome_dut120_descr"] = infos["diplome_dut120_descr"]
        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"],
                        date=aut["date"].isoformat() if aut["date"] else None,
                    )
                )
        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