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

"""Jury BUT et classiques: table recap annuelle et liens saisie
"""

import collections
import time
import numpy as np
from flask import g, url_for

from app.but import cursus_but
from app.but import jury_but
from app.but.jury_but import (
    DecisionsProposeesAnnee,
    DecisionsProposeesRCUE,
    DecisionsProposeesUE,
)
from app.comp.res_compat import NotesTableCompat
from app.models import ApcNiveau, UniteEns
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.scodoc.codes_cursus import (
    BUT_BARRE_RCUE,
    BUT_RCUE_SUFFISANT,
)
from app.scodoc import sco_utils as scu
from app.tables.recap import RowRecap, TableRecap


class TableJury(TableRecap):
    """Cette table recap reprend les colonnes du tableau recap, sauf les évaluations,
    et ajoute:
    Pour le BUT:
        - les RCUEs (moyenne et code décision)
    Pour toutes les formations:
        - les codes de décisions jury sur les UEs
        - le lien de saisie ou modif de la décision de jury
    """

    def __init__(self, *args, row_class: str = None, read_only=True, **kwargs):
        super().__init__(
            *args, row_class=row_class or RowJury, finalize=False, **kwargs
        )
        # redéclare pour VSCode
        self.rows: list["RowJury"] = self.rows
        self.res: NotesTableCompat = self.res
        self.read_only = read_only
        # Stats jury: fréquence de chaque code enregistré
        self.freq_codes_annuels = collections.Counter()
        # Ajout colonnes spécifiques à la table jury:
        if self.rows:  # non vide
            if self.res.is_apc:
                self.add_but_competences()
                self.add_rcues()
            self.add_jury()
            self.add_groups_header()
        # Termine la table
        self.finalize()

    def add_rcues(self):
        """Ajoute les colonnes indiquant le nb de RCUEs et chaque RCUE
        pour tous les étudiants de la table.
        La table contient des rows avec la clé etudid.
        Les colonnes ont la classe css "rcue".
        """
        self.insert_group("rcue", before="col_ues_validables")
        for row in self.rows:
            deca = row.deca
            if deca.code_valide:
                self.freq_codes_annuels[deca.code_valide] += 1
            row.add_nb_rcues_cell()
            # --- Les RCUEs
            for dec_rcue in deca.get_decisions_rcues_annee():
                if dec_rcue.rcue.complete:
                    row.add_rcue_cols(dec_rcue)
        self.freq_codes_annuels["total"] = len(self.rows)

    def add_jury(self):
        """Ajoute la colonne code jury et le lien.
        - Le code jury est celui du semestre: cette colonne n'est montrée
        que pour les formations classiques, ce code n'est pas utilisé en BUT.
        - En BUT, on donne la décision de jury annuelle.
        """
        res = self.res
        autorisations = res.get_autorisations_inscription()
        if res.is_apc:
            validations_annee = res.get_validations_annee()
        for row in self.rows:
            etud = row.etud
            if not res.is_apc:
                # formations classiques: code semestre
                if res.validations:
                    dec_sem = res.validations.decisions_jury.get(etud.id)
                    jury_code_sem = dec_sem["code"] if dec_sem else ""
                else:
                    jury_code_sem = ""
                row.add_cell(
                    "jury_code_sem",
                    "Jury",
                    jury_code_sem or "",
                    group="jury_code_sem",
                    classes=[] if jury_code_sem else ["empty_code"],
                )
                self.foot_title_row.cells["jury_code_sem"].target_attrs[
                    "title"
                ] = """Code jury sur le semestre"""
            # Autorisations inscription
            row.add_cell(
                "autorisations_inscription",
                "Passage",
                ", ".join("S" + str(i) for i in sorted(autorisations[etud.id]))
                if etud.id in autorisations
                else "",
                group="jury_code_sem",
                classes=["recorded_code"],
            )
            if res.is_apc:  # BUT
                validation_annee = validations_annee.get(etud.id, None)
                row.add_cell(
                    "decision_annuelle",
                    "Année",
                    validation_annee.code if validation_annee else "",
                    group="jury_code_sem",
                    classes=["recorded_code"],
                )
            # Lien saisie ou visu jury
            a_saisir = (not res.validations) or (not res.validations.has_decision(etud))
            row.add_cell(
                "jury_link",
                "",
                f"""{("➨ saisir" if a_saisir else "modifier")
                    if not self.read_only else "voir"} décisions""",
                group="col_jury_link",
                classes=["fontred"] if a_saisir else [],
                target=url_for(
                    "notes.formsemestre_validation_etud_form",
                    scodoc_dept=g.scodoc_dept,
                    formsemestre_id=res.formsemestre.id,
                    etudid=etud.id,
                ),
                target_attrs={"class": "stdlink"},
            )

    def add_but_competences(self):
        "Ajoute les colonnes résultats BUT (niveaux de compétences des 3 années)"
        prev_group = "cursus"
        for annee in ("BUT1", "BUT2", "BUT3"):
            group = f"cursus_{annee}"
            self.insert_group(group, after=prev_group)
            prev_group = group
            self.group_titles[group] = f"Compétences {annee}"
        for row in self.rows:
            etud = row.etud
            cursus_dict = cursus_but.EtudCursusBUT(
                etud, self.res.formsemestre.formation
            ).to_dict()
            first = True
            for competence_id in cursus_dict:
                for annee in ("BUT1", "BUT2", "BUT3"):
                    validation_rcue = cursus_dict[competence_id][annee]
                    if validation_rcue:
                        niveau: ApcNiveau = validation_rcue.niveau()
                        titre = f"C{niveau.competence.numero}"  # à voir (nommer les compétences...)
                        row.add_cell(
                            f"c_{competence_id}_{annee}",
                            titre,
                            validation_rcue.code,
                            group="cursus_" + annee,
                            classes=[],
                            column_classes=["cursus_but" + (" first" if first else "")],
                            target_attrs={
                                "title": f"{niveau.competence.titre} niveau {niveau.ordre}"
                            },
                        )
                        first = False


class RowJury(RowRecap):
    "Ligne de la table saisie jury"

    def __init__(self, table: TableJury, etud: Identite, *args, **kwargs):
        self.table: TableJury = table
        super().__init__(table, etud, *args, **kwargs)
        if table.res.is_apc:
            # Conserve le deca de cet étudiant:
            self.deca = jury_but.DecisionsProposeesAnnee(
                self.etud, self.table.res.formsemestre
            )

    def add_nb_rcues_cell(self):
        "cell avec nb niveaux validables / total"
        if self.table.res.get_etud_etat(self.id) != scu.INSCRIT:
            return  # rien pour DEM et DEF
        deca = self.deca
        classes = ["col_rcue", "col_rcues_validables"]
        if deca.nb_rcues_under_8 > 0:
            classes.append("moy_ue_warning")
        elif deca.nb_validables < deca.nb_competences:
            classes.append("moy_ue_inf")
        else:
            classes.append("moy_ue_valid")

        if len(deca.get_decisions_rcues_annee()) > 0:
            # permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
            if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
                moy = deca.res_pair.etud_moy_gen[deca.etud.id]
                if np.isnan(moy):
                    moy_gen_d = "x"
                else:
                    moy_gen_d = f"{int(moy*1000):05}"
            else:
                moy_gen_d = "x"
            order = f"{deca.nb_validables:04d}-{moy_gen_d}"
        else:
            # étudiants sans RCUE: pas de semestre impair, ...
            # les classe à la fin
            order = f"{deca.nb_validables:04d}-00000-{deca.etud.sort_key}"

        # RCUE pouvant être validés
        self.add_cell(
            "rcues_validables",
            "RCUEs",
            f"""{deca.nb_validables}/{deca.nb_competences}"""
            + ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
            raw_content=f"""{deca.nb_validables}/{deca.nb_competences}""",
            group="rcue",
            classes=classes,
            data={"order": order},
        )

    def add_ue_cols(self, ue: UniteEns, ue_status: dict, col_group: str = None):
        "Ajoute 2 colonnes: moyenne d'UE et code jury"
        # table recap standard (mais avec group différent)
        super().add_ue_cols(ue, ue_status, col_group=col_group or "col_ue")
        dues = self.table.res.get_etud_decisions_ue(self.etud.id)
        due = dues.get(ue.id) if dues else None

        col_id = f"moy_ue_{ue.id}_code"
        title = (
            f"""{ue.acronyme} enregistrée le {due['event_date']}, {
                (due["ects"] or 0):.3g}&nbsp;ECTS."""
            if due
            else """pas de décision"""
        )
        self.add_cell(
            col_id,
            "",  # titre vide
            due["code"] if due else "",
            raw_content=due["code"] if due else "",
            group="col_ue",
            classes=["recorded_code"],
            column_classes={"col_jury", "col_ue_code"},
            target_attrs={"title": title},
        )

    def add_rcue_cols(self, dec_rcue: DecisionsProposeesRCUE):
        "2 cells: moyenne du RCUE, code enregistré"
        rcue = dec_rcue.rcue
        if not rcue.complete:
            return
        col_id = f"moy_rcue_{rcue.niveau.id}"  # le niveau_id
        self.table.group_titles["rcue"] = "RCUEs en cours"
        note_class = ""
        val = rcue.moy_rcue
        if isinstance(val, float):
            if val < BUT_BARRE_RCUE:
                note_class = "moy_ue_inf"
            elif val >= BUT_BARRE_RCUE:
                note_class = "moy_ue_valid"
            if val < BUT_RCUE_SUFFISANT:
                note_class = "moy_ue_warning"  # notes très basses
        self.add_cell(
            col_id,
            f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
            self.table.fmt_note(val),
            raw_content=val,
            group="rcue",
            classes=[note_class],
            column_classes={"col_rcue"},
        )
        self.add_cell(
            col_id + "_code",
            f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
            dec_rcue.code_valide or "",
            group="rcue",
            classes=[
                "col_rcue_code",
                "recorded_code",
                "empty_code" if not dec_rcue.code_valide else "",
            ],
            column_classes={"col_rcue"},
        )

    # # --- Les ECTS validés
    # ects_valides = 0.0
    # if deca.res_impair:
    #     ects_valides += deca.res_impair.get_etud_ects_valides(etudid)
    # if deca.res_pair:
    #     ects_valides += deca.res_pair.get_etud_ects_valides(etudid)
    # row.add_cell(
    #     "ects_annee",
    #     "ECTS",
    #     f"""{int(ects_valides)}""",
    #     "col_code_annee",
    # )