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

"""Liste simple d'étudiants"""

import datetime
from flask import g, url_for
from flask_login import current_user
from app import log
from app.models import FormSemestre, Identite, Justificatif
from app.tables import table_builder as tb
from app.scodoc import sco_preferences
from app.scodoc import sco_utils as scu
import app.scodoc.sco_assiduites as scass
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission


class TableAssi(tb.Table):
    """Table listant les statistiques d'assiduité des étudiants
    L'id de la ligne est etudid, et le row stocke etud.

    On considère les assiduités entre les dates indiquées.

    Si formsemestre_modimpls est spécifié, restreint aux assiduités associées à des
    moduleimpls de ce formsemestre.

    Si convert_values, transforme les nombre en chaines ("12.34"), pour le html.
    """

    def __init__(
        self,
        etuds: list[Identite] = None,
        dates: tuple[str, str] = None,
        formsemestre: FormSemestre = None,
        formsemestre_modimpls: FormSemestre | None = None,
        convert_values=False,
        **kwargs,
    ):
        self.rows: list["RowAssi"] = []  # juste pour que VSCode nous aide sur .rows
        classes = ["gt_table"]
        try:
            self.dates = [
                datetime.datetime.fromisoformat(str(dates[0]) + "T00:00"),
                datetime.datetime.fromisoformat(str(dates[1]) + "T00:00"),
            ]
        except ValueError as exc:
            raise ScoValueError("invalid dates") from exc
        self.formsemestre = formsemestre
        self.formsemestre_modimpls = formsemestre_modimpls
        if convert_values:
            self.fmt_num = lambda x: f"{x:2.3g}"
        else:
            self.fmt_num = lambda x: x
        super().__init__(
            row_class=RowAssi,
            classes=classes,
            **kwargs,
            with_foot_titles=False,
        )
        self.add_etuds(etuds)

    def add_etuds(self, etuds: list[Identite]):
        "Ajoute des étudiants à la table"
        for etud in etuds:
            row = self.row_class(self, etud)
            row.add_etud_cols()
            self.add_row(row)


class RowAssi(tb.Row):
    "Ligne de la table assiduité"

    # pour le moment très simple, extensible (codes, liens bulletins, ...)
    def __init__(self, table: TableAssi, etud: Identite, *args, **kwargs):
        # Etat de l'inscription au formsemestre
        if "classes" not in kwargs:
            kwargs["classes"] = []
        try:
            inscription = table.formsemestre.etuds_inscriptions[etud.id]
            if inscription.etat == scu.DEMISSION:
                kwargs["classes"].append("etuddem")
        except KeyError:
            log(f"RowAssi: etudid {etud.id} non inscrit à {table.formsemestre.id}")
            kwargs["classes"].append("non_inscrit")  # ne devrait pas arriver !

        super().__init__(table, etud.id, *args, **kwargs)
        self.etud = etud
        self.dates = table.dates

    def add_etud_cols(self):
        """Ajoute les colonnes"""
        etud = self.etud
        fmt_num = self.table.fmt_num
        self.table.group_titles.update(
            {
                "etud_codes": "Codes",
                "identite_detail": "",
                "identite_court": "",
            }
        )

        bilan_etud = url_for(
            "assiduites.bilan_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
        )
        self.add_cell(
            "etudid",
            "etudid",
            etud.etudid,
            "etudinfo",
            only_excel=True,
        )
        self.add_cell(
            "code_nip",
            "code_nip",
            etud.code_nip,
            "etudinfo",
            only_excel=True,
        )
        self.add_cell(
            "nom_disp",
            "Nom",
            etud.nom_disp(),
            "etudinfo",
            attrs={"id": str(etud.id)},
            data={"order": etud.sort_key},
            target=bilan_etud,
            target_attrs={"class": "discretelink"},
        )
        self.add_cell(
            "prenom",
            "Prénom",
            etud.prenom_str,
            "etudinfo",
            attrs={"id": str(etud.id)},
            data={"order": etud.sort_key},
            target=bilan_etud,
            target_attrs={"class": "discretelink"},
        )
        stats = self._get_etud_stats(etud)
        for key, value in stats.items():
            if key == "present" and sco_preferences.get_preference(
                "non_present",
                dept_id=g.scodoc_dept_id,
                formsemestre_id=self.table.formsemestre.id,
            ):
                continue

            self.add_cell(key, value[0], fmt_num(value[1] - value[2]), "assi_stats")
            if key != "present":
                self.add_cell(
                    key + "_justi",
                    value[0] + " Justifiées",
                    fmt_num(value[2]),
                    "assi_stats",
                    column_classes={"col_count"},
                )

        compte_justificatifs = scass.filter_by_date(
            etud.justificatifs, Justificatif, self.dates[0], self.dates[1]
        )

        compte_justificatifs_att = compte_justificatifs.filter(Justificatif.etat == 2)

        self.add_cell(
            "justificatifs_att",
            "Justificatifs en Attente",
            fmt_num(compte_justificatifs_att.count()),
            column_classes={"col_count"},
        )
        self.add_cell(
            "justificatifs",
            "Justificatifs",
            fmt_num(compte_justificatifs.count()),
            column_classes={"col_count"},
        )

        if current_user.has_permission(Permission.AbsChange):
            ajout_url: str = url_for(
                "assiduites.ajout_assiduite_etud",
                scodoc_dept=g.scodoc_dept,
                etudid=etud.id,
                formsemestre_id=self.table.formsemestre.id,
            )
            self.add_cell(
                "lien_ajout",
                "",
                f"""<a href='{ajout_url}' class="stdlink">signaler</a>""",
                no_excel=True,
                column_classes={"col_lien_ajout"},
            )

    def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
        """
        Renvoie le comptage (dans la métrique du département) des différents états
        d'assiduité d'un étudiant.
        Considère les dates.

        Returns :
            {
                "<etat>" : [<Etat version lisible>, <nb total etat>, <nb just etat>]
            }

        """

        # Préparation du retour
        retour: dict[str, tuple[str, float, float]] = {
            "absent": ["Absences", 0.0, 0.0],
            "retard": ["Retards", 0.0, 0.0],
            "present": ["Présences", 0.0, 0.0],
        }

        # Récupération de la métrique du département
        assi_metric = scu.translate_assiduites_metric(
            sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
        )

        compte_etat: dict[str, dict] = scass.get_assiduites_stats(
            assiduites=etud.assiduites,
            metric=assi_metric,
            filtered={
                "date_debut": self.dates[0],
                "date_fin": self.dates[1],
                "etat": "absent,present,retard",  # pour tout compter d'un coup
                "formsemestre_modimpls": self.table.formsemestre_modimpls,
                "split": 1,  # afin d'avoir la division des stats en état, etatjust, etatnonjust
            },
        )

        # Pour chaque état on met à jour les valeurs de retour
        for etat, valeur in retour.items():
            valeur[1] = compte_etat[etat][assi_metric]
            if etat != "present":
                valeur[2] = compte_etat[etat]["justifie"][assi_metric]
        return retour


def etuds_sorted_from_ids(etudids) -> list[Identite]:
    "Liste triée d'etuds à partir d'une collections d'etudids"
    etuds = [Identite.get_etud(etudid) for etudid in etudids]
    return sorted(etuds, key=lambda etud: etud.sort_key)