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

"""Génération bulletin BUT au format PDF standard

La génération du bulletin PDF suit le chemin suivant:

- vue formsemestre_bulletinetud -> sco_bulletins.formsemestre_bulletinetud

    bul_dict = bulletin_but.BulletinBUT(formsemestre).bulletin_etud_complet(etud)

- sco_bulletins_generator.make_formsemestre_bulletin_etud()
- instance de BulletinGeneratorStandardBUT
- BulletinGeneratorStandardBUT.generate(fmt="pdf")
    sco_bulletins_generator.BulletinGenerator.generate()
    .generate_pdf()
    .bul_table() (ci-dessous)

"""
from reportlab.lib.colors import blue
from reportlab.lib.units import cm, mm
from reportlab.platypus import Paragraph, Spacer

from app.models import ScoDocSiteConfig
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
from app.scodoc import gen_tables
from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc import sco_utils as scu


class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
    """Génération du bulletin de BUT au format PDF.

    self.infos est le dict issu de BulletinBUT.bulletin_etud_complet()
    """

    # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur:
    list_in_menu = False
    scale_table_in_page = False  # pas de mise à l'échelle pleine page auto
    multi_pages = True  # plusieurs pages par bulletins
    small_fontsize = "8"

    def bul_table(self, fmt="html"):
        """Génère la table centrale du bulletin de notes
        Renvoie:
        - en HTML: une chaine
        - en PDF: une liste d'objets PLATYPUS (eg instance de Table).
        """
        if fmt == "pdf" and ScoDocSiteConfig.is_bul_pdf_disabled():
            return [Paragraph("<p>Export des PDF interdit par l'administrateur</p>")]
        tables_infos = [
            # ---- TABLE SYNTHESE UES
            self.but_table_synthese_ues(),
        ]
        if self.version != "short":
            tables_infos += [
                # ---- TABLE RESSOURCES
                self.but_table_ressources(),
                # ---- TABLE SAE
                self.but_table_saes(),
            ]
        objects = []
        for i, (col_keys, rows, pdf_style, col_widths) in enumerate(tables_infos):
            table = gen_tables.GenTable(
                rows=rows,
                columns_ids=col_keys,
                pdf_table_style=pdf_style,
                pdf_col_widths=[col_widths[k] for k in col_keys],
                preferences=self.preferences,
                html_class="notes_bulletin",
                html_class_ignore_default=True,
                html_with_td_classes=True,
            )
            table_objects = table.gen(fmt=fmt)
            objects += table_objects
            # objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
            if i != 2:
                objects.append(Spacer(1, 6 * mm))

        return objects

    def but_table_synthese_ues(
        self, title_bg=(182, 235, 255), title_ue_cap_bg=(150, 207, 147)
    ):
        """La table de synthèse; pour chaque UE, liste des ressources et SAÉs avec leurs notes
        et leurs coefs.
        Renvoie: colkeys, P, pdf_style, colWidths
        - colkeys: nom des colonnes de la table (clés)
        - P : table (liste de dicts de chaines de caracteres)
        - pdf_style : commandes table Platypus
        - largeurs de colonnes pour PDF
        """
        # nb: self.infos a ici été donné par BulletinBUT.bulletin_etud_complet()
        col_widths = {
            "titre": None,
            "min": 1.5 * cm,
            "moy": 1.5 * cm,
            "max": 1.5 * cm,
            "moyenne": 2 * cm,
            "coef": 2 * cm,
        }
        with_col_minmax = self.preferences["bul_show_minmax"]
        with_col_moypromo = self.preferences["bul_show_moypromo"]
        # Noms des colonnes à afficher:
        col_keys = ["titre"]
        if with_col_minmax:
            col_keys += ["min"]
        if with_col_moypromo:
            col_keys += ["moy"]
        if with_col_minmax:
            col_keys += ["max"]
        col_keys += ["coef", "moyenne"]
        # Couleur fond:
        title_bg = tuple(x / 255.0 for x in title_bg)
        title_ue_cap_bg = tuple(x / 255.0 for x in title_ue_cap_bg)
        # elems pour générer table avec gen_table (liste de dicts)
        rows = [
            # Ligne de titres
            {
                "titre": "Unités d'enseignement",
                "min": "Promotion",
                "moy": "",
                "max": "",
                "_min_colspan": 2,
                "moyenne": Paragraph("<para align=right><b>Note/20</b></para>"),
                "coef": "Coef.",
                "_coef_pdf": Paragraph("<para align=right><b><i>Coef.</i></b></para>"),
                "_css_row_class": "note_bold",
                "_pdf_row_markup": ["b"],
                "_pdf_style": [
                    ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                ],
            },
        ]
        if with_col_moypromo and not with_col_minmax:
            rows[-1]["moy"] = "Promotion"
        # 2eme ligne titres si nécessaire
        if with_col_minmax:  # TODO or with_col_abs:
            rows.append(
                {
                    "min": "min.",
                    "moy": "moy.",
                    "max": "max.",
                    # "abs": "(Tot. / J.)",
                    "_css_row_class": "note_bold",
                    "_pdf_row_markup": ["b"],
                    "_pdf_style": [
                        ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                    ],
                }
            )
        rows[-1]["_pdf_style"] += [
            ("BOTTOMPADDING", (0, 0), (-1, 0), 7),
            (
                "LINEBELOW",
                (0, 0),
                (-1, 0),
                self.PDF_LINEWIDTH,
                blue,
            ),
        ]
        ues = self.infos["ues"]
        ues_capitalisees = self.infos.get("ues_capitalisees", {})
        ues_tup = sorted(
            list(ues.items()) + list(ues_capitalisees.items()),
            key=lambda x: x[1]["numero"],
        )
        for ue_acronym, ue in ues_tup:
            is_capitalized = "date_capitalisation" in ue
            self._ue_rows(
                rows, ue_acronym, ue, title_ue_cap_bg if is_capitalized else title_bg
            )

        # Global pdf style commands:
        pdf_style = [
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("BOX", (0, 0), (-1, -1), 0.4, blue),  # ajoute cadre extérieur bleu:
        ]
        return col_keys, rows, pdf_style, col_widths

    def _ue_rows(self, rows: list, ue_acronym: str, ue: dict, title_bg: tuple):
        "Décrit une UE dans la table synthèse: titre, sous-titre et liste modules"
        if (ue["type"] == UE_SPORT) and len(ue.get("modules", [])) == 0:
            # ne mentionne l'UE que s'il y a des modules
            return
        # 1er ligne titre UE
        moy_ue = ue.get("moyenne", "-")
        if isinstance(moy_ue, dict):
            moy_ue = moy_ue.get("value", "-") if moy_ue is not None else "-"
        t = {
            "titre": f"{ue_acronym} - {ue['titre']}",
            "moyenne": Paragraph(
                f"""<para align=right><b>{moy_ue or "-"}</b></para>"""
            ),
            "_css_row_class": "note_bold",
            "_pdf_row_markup": ["b"],
            "_pdf_style": [
                (
                    "LINEABOVE",
                    (0, 0),
                    (-1, 0),
                    self.PDF_LINEWIDTH,
                    self.PDF_LINECOLOR,
                ),
                ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                ("BOTTOMPADDING", (0, 0), (-1, 0), 7),
            ],
        }
        rows.append(t)
        if ue["type"] == UE_SPORT:
            self.ue_sport_rows(rows, ue, title_bg)
        else:
            self.ue_std_rows(rows, ue, title_bg)

    @staticmethod
    def affichage_bonus_malus(ue: dict) -> list[str]:
        "liste de chaînes affichant les bonus et malus"
        fields_bmr = []
        # lecture des bonus sport culture et malus (ou bonus autre) (0 si valeur non numérique)
        try:
            bonus_sc = float(ue.get("bonus", 0.0)) or 0
        except ValueError:
            bonus_sc = 0
        try:
            malus = float(ue.get("malus", 0.0)) or 0
        except ValueError:
            malus = 0
        # Calcul de l affichage
        if malus < 0:
            if bonus_sc > 0:
                fields_bmr.append(f"Bonus sport/culture: {bonus_sc}")
                fields_bmr.append(f"Bonus autres: {-malus}")
            else:
                fields_bmr.append(f"Bonus: {-malus}")
        elif malus > 0:
            if bonus_sc > 0:
                fields_bmr.append(f"Bonus: {bonus_sc}")
            fields_bmr.append(f"Malus: {malus}")
        else:
            if bonus_sc > 0:
                fields_bmr.append(f"Bonus: {bonus_sc}")
        return fields_bmr

    def ue_std_rows(self, rows: list, ue: dict, title_bg: tuple):
        "Lignes décrivant une UE standard dans la table de synthèse"
        # 2eme ligne titre UE (bonus/malus/ects)
        if "ECTS" in ue:
            ects_txt = f'ECTS: {ue["ECTS"]["acquis"]:.3g} / {ue["ECTS"]["total"]:.3g}'
        else:
            ects_txt = ""
        # case Bonus/Malus/Rang "bmr"
        fields_bmr = BulletinGeneratorStandardBUT.affichage_bonus_malus(ue)
        moy_ue = ue.get("moyenne", "-")
        if isinstance(moy_ue, dict):  # UE non capitalisées
            if self.preferences["bul_show_ue_rangs"]:
                fields_bmr.append(
                    f"Rang: {ue['moyenne']['rang']} / {ue['moyenne']['total']}"
                )
            ue_min, ue_max, ue_moy = (
                ue["moyenne"]["min"],
                ue["moyenne"]["max"],
                ue["moyenne"]["moy"],
            )
        else:  # UE capitalisée
            ue_min, ue_max, ue_moy = "", "", moy_ue
            date_capitalisation = ue.get("date_capitalisation")
            if date_capitalisation:
                fields_bmr.append(
                    f"""Capitalisée le {date_capitalisation.strftime("%d/%m/%Y")}"""
                )
        t = {
            "titre": " - ".join(fields_bmr),
            "coef": ects_txt,
            "_coef_pdf": Paragraph(f"""<para align=right>{ects_txt}</para>"""),
            "_coef_colspan": 2,
            "_pdf_style": [
                ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                ("LINEBELOW", (0, 0), (-1, 0), self.PDF_LINEWIDTH, self.PDF_LINECOLOR),
                # ligne au dessus du bonus/malus, gris clair
                ("LINEABOVE", (0, 0), (-1, 0), self.PDF_LINEWIDTH, (0.7, 0.7, 0.7)),
            ],
            "min": ue_min,
            "max": ue_max,
            "moy": ue_moy,
        }
        rows.append(t)

        # Liste chaque ressource puis chaque SAE
        for mod_type in ("ressources", "saes"):
            for mod_code, mod in ue[mod_type].items():
                t = {
                    "titre": f"{mod_code} {self.infos[mod_type][mod_code]['titre']}",
                    "moyenne": Paragraph(f'<para align=right>{mod["moyenne"]}</para>'),
                    "coef": mod["coef"],
                    "_coef_pdf": Paragraph(
                        f"<para align=right><i>{mod['coef']}</i></para>"
                    ),
                    "_pdf_style": [
                        (
                            "LINEBELOW",
                            (0, 0),
                            (-1, 0),
                            self.PDF_LINEWIDTH,
                            (0.7, 0.7, 0.7),  # gris clair
                        )
                    ],
                }
                rows.append(t)

    def ue_sport_rows(self, rows: list, ue: dict, title_bg: tuple):
        "Lignes décrivant l'UE bonus dans la table de synthèse"
        # UE BONUS
        for mod_code, mod in ue["modules"].items():
            rows.append(
                {
                    "titre": f"{mod_code or ''} {mod['titre'] or ''}",
                }
            )
            self.evaluations_rows(rows, mod["evaluations"])

    def but_table_ressources(self):
        """La table de synthèse; pour chaque ressources, note et liste d'évaluations
        Renvoie: colkeys, P, pdf_style, colWidths
        """
        return self.bul_table_modules(
            mod_type="ressources", title="Ressources", title_bg=(248, 200, 68)
        )

    def but_table_saes(self):
        "table des SAEs"
        return self.bul_table_modules(
            mod_type="saes",
            title="Situations d'apprentissage et d'évaluation",
            title_bg=(198, 255, 171),
        )

    def bul_table_modules(self, mod_type=None, title="", title_bg=(248, 200, 68)):
        """Table ressources ou SAEs
        - colkeys: nom des colonnes de la table (clés)
        - P : table (liste de dicts de chaines de caracteres)
        - pdf_style : commandes table Platypus
        - largeurs de colonnes pour PDF
        """
        # UE à utiliser pour les poids (# colonne/UE)
        ue_infos = self.infos["ues"]
        ue_acros = list(
            [k for k in ue_infos if ue_infos[k]["type"] != UE_SPORT]
        )  # ['RT1.1', 'RT2.1', 'RT3.1']
        # Colonnes à afficher:
        col_keys = ["titre"] + ue_acros + ["coef", "moyenne"]
        # Largeurs des colonnes:
        col_widths = {
            "titre": None,
            # "poids": None,
            "moyenne": 2 * cm,
            "coef": 2 * cm,
        }
        for ue_acro in ue_acros:
            col_widths[ue_acro] = 12 * mm  # largeur col. poids

        title_bg = tuple(x / 255.0 for x in title_bg)
        # elems pour générer table avec gen_table (liste de dicts)
        # Ligne de titres
        t = {
            "titre": title,
            # "_titre_colspan": 1 + len(ue_acros),
            "moyenne": "Note/20",
            "coef": "Coef.",
            "_coef_pdf": Paragraph("<para align=right><i>Coef.</i></para>"),
            "_css_row_class": "note_bold",
            "_pdf_row_markup": ["b"],
            "_pdf_style": [
                ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                ("BOTTOMPADDING", (0, 0), (-1, 0), 7),
                (
                    "LINEBELOW",
                    (0, 0),
                    (-1, 0),
                    self.PDF_LINEWIDTH,
                    blue,
                ),
            ],
        }
        for ue_acro in ue_acros:
            t[ue_acro] = Paragraph(
                f"<para align=right fontSize={self.small_fontsize}><i>{ue_acro}</i></para>"
            )
        rows = [t]
        for mod_code, mod in self.infos[mod_type].items():
            # 1er ligne titre module
            t = {
                "titre": f"{mod_code} - {mod['titre']}",
                "_titre_colspan": 2 + len(ue_acros),
                "_css_row_class": "note_bold",
                "_pdf_row_markup": ["b"],
                "_pdf_style": [
                    (
                        "LINEABOVE",
                        (0, 0),
                        (-1, 0),
                        self.PDF_LINEWIDTH,
                        self.PDF_LINECOLOR,
                    ),
                    ("BACKGROUND", (0, 0), (-1, 0), title_bg),
                    ("BOTTOMPADDING", (0, 0), (-1, 0), 7),
                ],
            }
            rows.append(t)
            # Evaluations:
            self.evaluations_rows(rows, mod["evaluations"], ue_acros)

        # Global pdf style commands:
        pdf_style = [
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("BOX", (0, 0), (-1, -1), 0.4, blue),  # ajoute cadre extérieur bleu:
        ]
        return col_keys, rows, pdf_style, col_widths

    def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
        "lignes des évaluations"
        for e in evaluations:
            coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*"
            t = {
                "titre": f"{e['description'] or ''}",
                "moyenne": e["note"]["value"],
                "_moyenne_pdf": Paragraph(
                    f"""<para align=right>{e["note"]["value"]}</para>"""
                ),
                "coef": coef,
                "_coef_pdf": Paragraph(
                    f"<para align=right fontSize={self.small_fontsize}><i>{coef}</i></para>"
                ),
                "_pdf_style": [
                    (
                        "LINEBELOW",
                        (0, 0),
                        (-1, 0),
                        self.PDF_LINEWIDTH,
                        (0.7, 0.7, 0.7),  # gris clair
                    )
                ],
            }
            col_idx = 1  # 1ere col. poids
            for ue_acro in ue_acros:
                t[ue_acro] = Paragraph(
                    f"""<para align=right fontSize={self.small_fontsize}><i>{
                        e["poids"].get(ue_acro, "") or ""}</i></para>"""
                )
                t["_pdf_style"].append(
                    (
                        "BOX",
                        (col_idx, 0),
                        (col_idx, 0),
                        self.PDF_LINEWIDTH,
                        (0.7, 0.7, 0.7),  # gris clair
                    ),
                )
                col_idx += 1
            rows.append(t)
