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

"""Génération bulletin BUT PDF synthétique en une page

On génère du PDF avec reportLab en utilisant les classes
ScoDoc BulletinGenerator et GenTable.

"""
import datetime
from flask_login import current_user

from reportlab.lib import styles
from reportlab.lib.colors import black, white, Color
from reportlab.lib.enums import TA_CENTER
from reportlab.lib.units import mm
from reportlab.platypus import Paragraph, Spacer, Table

from app.but import cursus_but
from app.models import (
    BulAppreciations,
    FormSemestre,
    Identite,
    ScolarFormSemestreValidation,
)

from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
from app.scodoc.sco_logos import Logo
from app.scodoc.sco_pdf import PDFLOCK, SU
from app.scodoc.sco_preferences import SemPreferences


def make_bulletin_but_court_pdf(
    args: dict,
    stand_alone: bool = True,
) -> bytes:
    """génère le bulletin court BUT en pdf.
    Si stand_alone, génère un doc pdf complet (une page ici),
    sinon un morceau (fragment) à intégrer dans un autre document.

    args donne toutes les infos du contenu du bulletin:
        bul: dict = None,
        cursus: cursus_but.EtudCursusBUT = None,
        decision_ues: dict = None,
        ects_total: float = 0.0,
        etud: Identite = None,
        formsemestre: FormSemestre = None,
        logo: Logo = None,
        prefs: SemPreferences = None,
        title: str = "",
        ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None,
        ues_acronyms: list[str] = None,
    """
    # A priori ce verrou n'est plus nécessaire avec Flask (multi-process)
    # mais...
    try:
        PDFLOCK.acquire()
        bul_generator = BulletinGeneratorBUTCourt(**args)
        bul_pdf = bul_generator.generate(fmt="pdf", stand_alone=stand_alone)
    finally:
        PDFLOCK.release()
    return bul_pdf


class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
    """Ce générateur de bulletin BUT court est assez différent des autres bulletins.
    Ne génére que du PDF.
    Il reprend la mise en page et certains éléments (pied de page, signature).
    """

    # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur:
    list_in_menu = False
    scale_table_in_page = True  # pas de mise à l'échelle pleine page auto
    multi_pages = False  # une page par bulletin
    small_fontsize = "8"
    color_blue_bg = Color(0, 153 / 255, 204 / 255)
    color_gray_bg = Color(0.86, 0.86, 0.86)

    def __init__(
        self,
        bul: dict = None,
        cursus: cursus_but.EtudCursusBUT = None,
        decision_ues: dict = None,
        ects_total: float = 0.0,
        etud: Identite = None,
        formsemestre: FormSemestre = None,
        logo: Logo = None,
        prefs: SemPreferences = None,
        title: str = "",
        ue_validation_by_niveau: dict[
            tuple[int, str], ScolarFormSemestreValidation
        ] = None,
        ues_acronyms: list[str] = None,
    ):
        super().__init__(bul, authuser=current_user)
        self.bul = bul
        self.cursus = cursus
        self.decision_ues = decision_ues
        self.ects_total = ects_total
        self.etud = etud
        self.formsemestre = formsemestre
        self.logo = logo
        self.prefs = prefs
        self.title = title
        self.ue_validation_by_niveau = ue_validation_by_niveau
        self.ues_acronyms = ues_acronyms  # sans UEs sport

        self.nb_ues = len(self.ues_acronyms)
        # Styles PDF
        self.style_base = styles.ParagraphStyle("style_base")
        self.style_base.fontName = "Helvetica"
        self.style_base.fontSize = 9
        self.style_base.firstLineIndent = 0
        # écrase style defaut des bulletins
        self.style_field = self.style_base

        # Le nom/prénom de l'étudiant:
        self.style_nom = styles.ParagraphStyle("style_nom", self.style_base)
        self.style_nom.fontSize = 11
        self.style_nom.fontName = "Helvetica-Bold"

        self.style_cell = styles.ParagraphStyle("style_cell", self.style_base)
        self.style_cell.fontSize = 7
        self.style_cell.leading = 7
        self.style_cell_bold = styles.ParagraphStyle("style_cell_bold", self.style_cell)
        self.style_cell_bold.fontName = "Helvetica-Bold"

        self.style_head = styles.ParagraphStyle("style_head", self.style_cell_bold)
        self.style_head.fontSize = 9

        self.style_niveaux = styles.ParagraphStyle("style_niveaux", self.style_cell)
        self.style_niveaux.alignment = TA_CENTER
        self.style_niveaux.leading = 9
        self.style_niveaux.firstLineIndent = 0
        self.style_niveaux.leftIndent = 1
        self.style_niveaux.rightIndent = 1
        self.style_niveaux.borderWidth = 0.5
        self.style_niveaux.borderPadding = 2
        self.style_niveaux.borderRadius = 2
        self.style_niveaux_top = styles.ParagraphStyle(
            "style_niveaux_top", self.style_niveaux
        )
        self.style_niveaux_top.fontName = "Helvetica-Bold"
        self.style_niveaux_top.fontSize = 8
        self.style_niveaux_titre = styles.ParagraphStyle(
            "style_niveaux_titre", self.style_niveaux
        )
        self.style_niveaux_titre.textColor = white
        self.style_niveaux_titre.backColor = self.color_blue_bg
        self.style_niveaux_titre.borderColor = self.color_blue_bg

        self.style_niveaux_code = styles.ParagraphStyle(
            "style_niveaux_code", self.style_niveaux
        )
        self.style_niveaux_code.borderColor = black
        #
        self.style_jury = styles.ParagraphStyle("style_jury", self.style_base)
        self.style_jury.fontSize = 9
        self.style_jury.leading = self.style_jury.fontSize * 1.4  # espace les lignes
        self.style_jury.backColor = self.color_gray_bg
        self.style_jury.borderColor = black
        self.style_jury.borderWidth = 1
        self.style_jury.borderPadding = 2
        self.style_jury.borderRadius = 2

        self.style_appreciations = styles.ParagraphStyle(
            "style_appreciations", self.style_base
        )
        self.style_appreciations.fontSize = 9
        self.style_appreciations.leading = (
            self.style_jury.fontSize * 1.4
        )  # espace les lignes

        self.style_assiduite = self.style_cell
        self.style_signature = self.style_appreciations

        # Géométrie page
        self.width_page_avail = 185 * mm  # largeur utilisable
        # Géométrie tableaux
        self.width_col_ue = 18 * mm
        self.width_col_ue_titres = 16.5 * mm
        # Modules
        self.width_col_code = self.width_col_ue
        # Niveaux
        self.width_col_niveaux_titre = 24 * mm
        self.width_col_niveaux_code = 14 * mm

    def bul_title_pdf(self, preference_field="bul_but_pdf_title") -> list:
        """Génère la partie "titre" du bulletin de notes.
        Renvoie une liste d'objets platypus
        """
        # comme les bulletins standard, mais avec notre préférence
        return super().bul_title_pdf(preference_field=preference_field)

    def bul_part_below(self, fmt="pdf") -> list:
        """Génère les informations placées sous la table
        Dans le cas du bul. court BUT pdf, seulement les appréciations.
        fmt est ignoré ici.
        """
        appreciations = BulAppreciations.get_appreciations_list(
            self.formsemestre.id, self.etud.id
        )
        return (
            [
                Spacer(1, 3 * mm),
                self.bul_appreciations_pdf(
                    appreciations, style=self.style_appreciations
                ),
            ]
            if appreciations
            else []
        )

    def bul_table(self, fmt=None) -> list:
        """Génère la table centrale du bulletin de notes
        Renvoie: une liste d'objets PLATYPUS (eg instance de Table).
        L'argument fmt est ici ignoré (toujours en PDF)
        """
        style_table_2cols = [
            ("ALIGN", (0, -1), (0, -1), "LEFT"),
            ("ALIGN", (-1, -1), (-1, -1), "RIGHT"),
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("LEFTPADDING", (0, 0), (-1, -1), 0),
            ("TOPPADDING", (0, 0), (-1, -1), 0),
            ("RIGHTPADDING", (0, 0), (-1, -1), 0),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 0),
        ]
        # Ligne avec boite assiduité et table UEs
        table_abs_ues = Table(
            [
                [
                    self.boite_identite() + [Spacer(1, 3 * mm), self.boite_assiduite()],
                    self.table_ues(),
                ],
            ],
            style=style_table_2cols,
        )
        table_abs_ues.hAlign = "RIGHT"
        # Ligne (en bas) avec table cursus et boite jury
        table_cursus_jury = Table(
            [
                [
                    self.table_cursus_but(),
                    [Spacer(1, 8 * mm), self.boite_decisions_jury()],
                ]
            ],
            colWidths=(self.width_page_avail - 84 * mm, 84 * mm),
            style=style_table_2cols,
        )
        return [
            table_abs_ues,
            Spacer(0, 3 * mm),
            self.table_ressources(),
            Spacer(0, 3 * mm),
            self.table_saes(),
            Spacer(0, 5 * mm),
            table_cursus_jury,
        ]

    def table_ues(self) -> Table:
        """Table avec les résultats d'UE du semestre courant"""
        bul = self.bul
        rows = [
            [
                f"Unités d'enseignement du semestre {self.formsemestre.semestre_id}",
            ],
            [""] + self.ues_acronyms,
            ["Moyenne"]
            + [bul["ues"][ue]["moyenne"]["value"] for ue in self.ues_acronyms],
            ["dont bonus"]
            + [
                bul["ues"][ue]["bonus"] if bul["ues"][ue]["bonus"] != "00.00" else ""
                for ue in self.ues_acronyms
            ],
            ["et malus"]
            + [
                bul["ues"][ue]["malus"] if bul["ues"][ue]["malus"] != "00.00" else ""
                for ue in self.ues_acronyms
            ],
            ["Rang"]
            + [
                f'{bul["ues"][ue]["moyenne"]["rang"]} / {bul["ues"][ue]["moyenne"]["total"]}'
                for ue in self.ues_acronyms
            ],
        ]
        if self.prefs["bul_show_ects"]:
            rows += [
                ["ECTS"]
                + [
                    f'{bul["ues"][ue]["ECTS"]["acquis"]:g} /{bul["ues"][ue]["ECTS"]["total"]:g}'
                    for ue in self.ues_acronyms
                ]
            ]
        rows += [
            ["Jury"]
            + [
                self.decision_ues[ue]["code"] if ue in self.decision_ues else ""
                for ue in self.ues_acronyms
            ]
        ]
        blue_bg = Color(183 / 255.0, 235 / 255.0, 255 / 255.0)
        table_style = [
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("BOX", (0, 0), (-1, -1), 1.0, black),  # ajoute cadre extérieur
            ("INNERGRID", (0, 0), (-1, -1), 0.25, black),
            ("LEADING", (0, 1), (-1, -1), 5),
            ("SPAN", (0, 0), (self.nb_ues, 0)),
            ("BACKGROUND", (0, 0), (self.nb_ues, 0), blue_bg),
        ]
        col_widths = [self.width_col_ue_titres] + [self.width_col_ue] * self.nb_ues

        rows_styled = [[Paragraph(SU(str(cell)), self.style_head) for cell in rows[0]]]
        rows_styled += [
            [Paragraph(SU(str(cell)), self.style_cell_bold) for cell in rows[1]]
        ]
        rows_styled += [
            [Paragraph(SU(str(cell)), self.style_cell) for cell in row]
            for row in rows[2:-1]
        ]
        rows_styled += [
            [Paragraph(SU(str(cell)), self.style_cell_bold) for cell in rows[-1]]
        ]
        table = Table(
            rows_styled,
            colWidths=col_widths,
            style=table_style,
        )
        table.hAlign = "RIGHT"
        return table

    def _table_modules(self, mod_type: str = "ressources", title: str = "") -> Table:
        "génère table des modules: resources ou SAEs"
        bul = self.bul
        rows = [
            ["", "", "Unités d'enseignement"] + [""] * (self.nb_ues - 1),
            [title, ""] + self.ues_acronyms,
        ]
        for mod in self.bul[mod_type]:
            row = [mod, bul[mod_type][mod]["titre"]]
            row += [
                bul["ues"][ue][mod_type][mod]["moyenne"]
                if mod in bul["ues"][ue][mod_type]
                else ""
                for ue in self.ues_acronyms
            ]
            rows.append(row)

        title_bg = (
            Color(255 / 255, 192 / 255, 0)
            if mod_type == "ressources"
            else Color(176 / 255, 255 / 255, 99 / 255)
        )

        table_style = [
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("BOX", (0, 0), (-1, -1), 1.0, black),  # ajoute cadre extérieur
            ("INNERGRID", (0, 0), (-1, -1), 0.25, black),
            ("LEADING", (0, 1), (-1, -1), 5),
            # 1ère ligne titre
            ("SPAN", (0, 0), (1, 0)),
            ("SPAN", (2, 0), (self.nb_ues, 0)),
            # 2ème ligne titre
            ("SPAN", (0, 1), (1, 1)),
            ("BACKGROUND", (0, 1), (1, 1), title_bg),
        ]
        # Estime l'espace horizontal restant pour les titres de modules
        width_col_titre_module = (
            self.width_page_avail
            - self.width_col_code
            - self.width_col_ue * self.nb_ues
        )
        col_widths = [self.width_col_code, width_col_titre_module] + [
            self.width_col_ue
        ] * self.nb_ues

        rows_styled = [
            [Paragraph(SU(str(cell)), self.style_cell_bold) for cell in row]
            for row in rows[:2]
        ]
        rows_styled += [
            [Paragraph(SU(str(cell)), self.style_cell) for cell in row]
            for row in rows[2:]
        ]
        table = Table(
            rows_styled,
            colWidths=col_widths,
            style=table_style,
        )
        table.hAlign = "RIGHT"
        return table

    def table_ressources(self) -> Table:
        "La table des ressources"
        return self._table_modules("ressources", "Ressources")

    def table_saes(self) -> Table:
        "La table des SAEs"
        return self._table_modules(
            "saes", "Situations d'Apprentissage et d'Évaluation (SAÉ)"
        )

    def boite_identite(self) -> list:
        "Les informations sur l'identité et l'inscription de l'étudiant"
        return [
            Paragraph(
                SU(f"""{self.etud.nomprenom}"""),
                style=self.style_nom,
            ),
            Paragraph(
                SU(
                    f"""
            <b>{self.bul["demission"]}</b><br/>
            Formation: {self.formsemestre.titre_num()}<br/>
            Année scolaire: {self.formsemestre.annee_scolaire_str()}<br/>
            """
                ),
                style=self.style_base,
            ),
        ]

    def boite_assiduite(self) -> Table:
        "Les informations sur l'assiduité"
        if not self.bul["options"]["show_abs"]:
            return Paragraph("")  # empty
        color_bg = Color(245 / 255, 237 / 255, 200 / 255)
        rows = [
            ["Absences", ""],
            [f'({self.bul["semestre"]["absences"]["metrique"]})', ""],
            ["Non justifiées", self.bul["semestre"]["absences"]["injustifie"]],
            ["Total", self.bul["semestre"]["absences"]["total"]],
        ]
        rows_styled = [
            [Paragraph(SU(str(cell)), self.style_head) for cell in row]
            for row in rows[:1]
        ]
        rows_styled += [
            [Paragraph(SU(str(cell)), self.style_assiduite) for cell in row]
            for row in rows[1:]
        ]
        table = Table(
            rows_styled,
            # [topLeft, topRight, bottomLeft bottomRight]
            cornerRadii=[2 * mm] * 4,
            style=[
                ("BACKGROUND", (0, 0), (-1, -1), color_bg),
                ("SPAN", (0, 0), (1, 0)),
                ("SPAN", (0, 1), (1, 1)),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ],
            colWidths=(25 * mm, 10 * mm),
        )
        table.hAlign = "LEFT"
        return table

    def table_cursus_but(self) -> Table:
        "La table avec niveaux et validations BUT1, BUT2, BUT3"
        rows = [
            ["", "BUT 1", "BUT 2", "BUT 3"],
        ]
        for competence_id in self.cursus.to_dict():
            row = [self.cursus.competences[competence_id].titre]
            for annee in ("BUT1", "BUT2", "BUT3"):
                validation = self.cursus.validation_par_competence_et_annee.get(
                    competence_id, {}
                ).get(annee)
                has_niveau = self.cursus.competence_annee_has_niveau(
                    competence_id, annee
                )
                txt = ""
                if validation:
                    txt = validation.code
                elif has_niveau:
                    txt = "-"
                row.append(txt)
            rows.append(row)

        rows_styled = [
            [Paragraph(SU(str(cell)), self.style_niveaux_top) for cell in rows[0]]
        ] + [
            [Paragraph(SU(str(row[0])), self.style_niveaux_titre)]
            + [
                Paragraph(SU(str(cell)), self.style_niveaux_code) if cell else ""
                for cell in row[1:]
            ]
            for row in rows[1:]
        ]

        table = Table(
            rows_styled,
            colWidths=[
                self.width_col_niveaux_titre,
                self.width_col_niveaux_code,
                self.width_col_niveaux_code,
                self.width_col_niveaux_code,
            ],
            style=[
                ("ALIGN", (0, 0), (-1, -1), "CENTER"),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                ("LEFTPADDING", (0, 0), (-1, -1), 5),
                ("TOPPADDING", (0, 0), (-1, -1), 4),
                ("RIGHTPADDING", (0, 0), (-1, -1), 5),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
                # sert de séparateur entre les lignes:
                ("LINEABOVE", (0, 1), (-1, -1), 3, white),
                # séparateur colonne
                ("LINEBEFORE", (1, 1), (-1, -1), 5, white),
            ],
        )
        table.hAlign = "LEFT"
        return table

    def boite_decisions_jury(self):
        """La boite en bas à droite avec jury"""
        txt = f"""ECTS acquis en BUT : <b>{self.ects_total:g}</b><br/>"""
        if self.bul["semestre"].get("decision_annee", None):
            txt += f"""
            Décision saisie le {
                datetime.datetime.fromisoformat(self.bul["semestre"]["decision_annee"]["date"]).strftime("%d/%m/%Y")
            }, année BUT{self.bul["semestre"]["decision_annee"]["ordre"]}
            <b>{self.bul["semestre"]["decision_annee"]["code"]}</b>.
            <br/>
            """
        if self.bul["semestre"].get("autorisation_inscription", None):
            txt += (
                "<br/>Autorisé à s'inscrire en <b>"
                + ", ".join(
                    [
                        f"S{aut['semestre_id']}"
                        for aut in self.bul["semestre"]["autorisation_inscription"]
                    ]
                )
                + "</b>."
            )

        return Paragraph(txt, style=self.style_jury)