import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage

from app.models import FormSemestre
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date


TYPES_RCS = {
    "S1": {
        "aggregat": ["S1"],
        "descr": "Semestre 1 (S1)",
    },
    "S2": {
        "aggregat": ["S2"],
        "descr": "Semestre 2 (S2)",
    },
    "1A": {
        "aggregat": ["S1", "S2"],
        "descr": "BUT1 (S1+S2)",
    },
    "S3": {
        "aggregat": ["S3"],
        "descr": "Semestre 3 (S3)",
    },
    "S4": {
        "aggregat": ["S4"],
        "descr": "Semestre 4 (S4)",
    },
    "2A": {
        "aggregat": ["S3", "S4"],
        "descr": "BUT2 (S3+S4)",
    },
    "3S": {
        "aggregat": ["S1", "S2", "S3"],
        "descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)",
    },
    "4S": {
        "aggregat": ["S1", "S2", "S3", "S4"],
        "descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)",
    },
    "S5": {
        "aggregat": ["S5"],
        "descr": "Semestre 5 (S5)",
    },
    "S6": {
        "aggregat": ["S6"],
        "descr": "Semestre 6 (S6)",
    },
    "3A": {
        "aggregat": ["S5", "S6"],
        "descr": "3ème année (S5+S6)",
    },
    "5S": {
        "aggregat": ["S1", "S2", "S3", "S4", "S5"],
        "descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)",
    },
    "6S": {
        "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"],
        "descr": "Moyenne globale (S1+S2+S3+S4+S5+S6)",
    },
}
"""Dictionnaire détaillant les différents regroupements cohérents 
de semestres (RCS), en leur attribuant un nom et en détaillant 
le nom des semestres qu'ils regroupement et l'affichage qui en sera fait
dans les tableurs de synthèse"""

TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS.keys() if not cle.startswith("S")]
TOUS_LES_RCS = list(TYPES_RCS.keys())
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS.keys() if cle.startswith("S")]


class RCS:
    def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
        """Modélise un ensemble de semestres d'étudiants
        associé à un type de regroupement cohérent de semestres
        donné (par ex: 'S2', '3S', '2A').

        Si le RCS est un semestre de type Si, stocke le (ou les)
        formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si
        (en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)

        Pour le RCS de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie
        les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
        terminal de la trajectoire (par ex: ici un S3).

        Ces semestres peuvent être :

        * des S1+S2+S1+S2+S3 si redoublement de la 1ère année
        * des S1+S2+(année de césure)+S3 si césure, ...

        Args:
            nom_rcs: Un nom du RCS (par ex: '5S')
            semestre_final: Le semestre final du RCS
        """
        self.nom = nom_rcs
        """Nom du RCS"""

        self.formsemestre_final = semestre_final
        """FormSemestre terminal du RCS"""

        self.rcs_id = (nom_rcs, semestre_final.formsemestre_id)
        """Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""

        self.semestres_aggreges = {}
        """Semestres regroupés dans le RCS"""

    def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
        """Ajout de semestres aux semestres à regrouper

        Args:
            semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter
        """
        self.semestres_aggreges = self.semestres_aggreges | semestres

    def get_repr(self, verbose=True) -> str:
        """Représentation textuelle d'un RCS
        basé sur ses semestres aggrégés"""

        noms = []
        for fid in self.semestres_aggreges:
            semestre = self.semestres_aggreges[fid]
            noms.append(f"S{semestre.semestre_id}({fid})")
        noms = sorted(noms)
        repr = f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
        if verbose and noms:
            repr += " - " + "+".join(noms)
        return repr


class RCSsJuryPE:
    def __init__(self, annee_diplome: int):
        """Classe centralisant toutes les regroupements cohérents de
        semestres (RCS) des étudiants à prendre en compte dans un jury PE

        Args:
            annee_diplome: L'année de diplomation
        """

        self.annee_diplome = annee_diplome
        """Année de diplômation"""

        self.rcss: dict[tuple:RCS] = {}
        """Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCS}"""

        self.suivi: dict[int:str] = {}
        """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
            son RCS : {etudid: {nom_RCS: RCS}}"""

    def cree_rcss(self, etudiants: EtudiantsJuryPE):
        """Créé tous les RCS, au regard du cursus des étudiants
        analysés + les mémorise dans les données de l'étudiant

        Args:
            etudiants: Les étudiants à prendre en compte dans le Jury PE
        """

        for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
            """L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)"""
            noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"]
            nom_semestre_terminal = noms_semestre_de_aggregat[-1]

            for etudid in etudiants.cursus:
                if etudid not in self.suivi:
                    self.suivi[etudid] = {
                        aggregat: None
                        for aggregat in pe_comp.TOUS_LES_SEMESTRES
                                        + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
                    }

                """Le formsemestre terminal (dernier en date) associé au 
                semestre marquant la fin de l'aggrégat 
                (par ex: son dernier S3 en date)"""
                semestres = etudiants.cursus[etudid][nom_semestre_terminal]
                if semestres:
                    formsemestre_final = get_dernier_semestre_en_date(semestres)

                    """Ajout ou récupération de la trajectoire"""
                    trajectoire_id = (nom_rcs, formsemestre_final.formsemestre_id)
                    if trajectoire_id not in self.rcss:
                        trajectoire = RCS(nom_rcs, formsemestre_final)
                        self.rcss[trajectoire_id] = trajectoire
                    else:
                        trajectoire = self.rcss[trajectoire_id]

                    """La liste des semestres de l'étudiant à prendre en compte
                    pour cette trajectoire"""
                    semestres_a_aggreger = get_rcs_etudiant(
                        etudiants.cursus[etudid], formsemestre_final, nom_rcs
                    )

                    """Ajout des semestres à la trajectoire"""
                    trajectoire.add_semestres_a_aggreger(semestres_a_aggreger)

                    """Mémoire la trajectoire suivie par l'étudiant"""
                    self.suivi[etudid][nom_rcs] = trajectoire


def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
                     ):
    """Ensemble des semestres parcourus par un étudiant, connaissant
    les semestres de son cursus,
    dans le cadre du RCS visé et ayant pour semestre terminal `formsemestre_final`.

    Si le RCS est de type "Si", limite les semestres à ceux de numéro i.
    Par ex: si formsemestre_terminal est un S3 et nom_agrregat "S3", ne prend en compte que les
    semestres 3.

    Si le RCS est de type "iA" ou "iS" (incluant plusieurs numéros de semestres), prend en
    compte les dit numéros de semestres.

    Par ex: si formsemestre_terminal est un S3, ensemble des S1,
    S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1,
    ou S2, ou S3 s'il a redoublé).

    Les semestres parcourus sont antérieurs (en terme de date de fin)
    au formsemestre_terminal.

    Args:
        cursus: Dictionnaire {fid: FormSemestre(fid)} donnant l'ensemble des semestres
                dans lesquels l'étudiant a été inscrit
        formsemestre_final: le semestre final visé
        nom_rcs: Nom du RCS visé
    """
    numero_semestre_terminal = formsemestre_final.semestre_id
    # semestres_significatifs = self.get_semestres_significatifs(etudid)
    semestres_significatifs = {}
    for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1):
        semestres_significatifs = semestres_significatifs | semestres[f"S{i}"]

    if nom_rcs.startswith("S"):  # les semestres
        numero_semestres_possibles = [numero_semestre_terminal]
    elif nom_rcs.endswith("A"):  # les années
        numero_semestres_possibles = [
            int(sem[-1]) for sem in TYPES_RCS[nom_rcs]["aggregat"]
        ]
        assert numero_semestre_terminal in numero_semestres_possibles
    else:  # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal)
        numero_semestres_possibles = list(range(1, numero_semestre_terminal + 1))

    semestres_aggreges = {}
    for fid, semestre in semestres_significatifs.items():
        # Semestres parmi ceux de n° possibles & qui lui sont antérieurs
        if (
            semestre.semestre_id in numero_semestres_possibles
            and semestre.date_fin <= formsemestre_final.date_fin
        ):
            semestres_aggreges[fid] = semestre
    return semestres_aggreges

def get_descr_rcs(nom_rcs: str) -> str:
    """Renvoie la description pour les tableurs de synthèse
    Excel d'un nom de RCS"""
    return TYPES_RCS[nom_rcs]["descr"]
