import app.pe.pe_comp
from app.pe.rcss import pe_rcs, pe_trajectoires, pe_rcsemx
import app.pe.pe_etudiant as pe_etudiant
import app.pe.pe_comp as pe_comp
from app.models import FormSemestre
from app.pe import pe_affichage


class RCSsJuryPE:
    """Classe centralisant tous les regroupements cohérents de
    semestres (RCS) des étudiants à prendre en compte dans un jury PE

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

    def __init__(self, annee_diplome: int, etudiants: pe_etudiant.EtudiantsJuryPE):
        self.annee_diplome = annee_diplome
        """Année de diplômation"""

        self.etudiants = etudiants
        """Les étudiants recensés"""

        self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {}
        """Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)"""

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

        self.semXs: dict[tuple(int, str) : pe_trajectoires.SemX] = {}
        """Ensemble des SemX recensés (regroupement de (form)semestre BUT de rang x) : 
        {(nom_RCS, fid_terminal): SemX}"""

        self.semXs_suivis: dict[int:dict] = {}
        """Dictionnaire associant, pour chaque étudiant et pour chaque RCS de type Sx,
            son SemX : {etudid: {nom_RCS_de_type_Sx: SemX}}"""

        self.rcsemxs: dict[tuple(int, str) : pe_rcsemx.RCSemX] = {}
        """Ensemble des RCSemX (regroupement de SemX donnant les résultats aux sems de rang x)
        recensés : {(nom_RCS, fid_terminal): RCSemX}"""

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

    def cree_trajectoires(self):
        """Créé toutes les trajectoires, 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
        """

        tous_les_aggregats = pe_rcs.TOUS_LES_RCS

        for etudid in self.etudiants.cursus:
            self.trajectoires_suivies[etudid] = self.etudiants.trajectoires[etudid]

        for nom_rcs in tous_les_aggregats:
            # 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_semestres = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
            nom_semestre_final = noms_semestres[-1]

            for etudid in self.etudiants.cursus:
                # Le (ou les) semestre(s) marquant la fin du cursus de l'étudiant
                sems_final = self.etudiants.cursus[etudid][nom_semestre_final]
                if sems_final:
                    # Le formsemestre final (dernier en date) de l'étudiant,
                    # marquant la fin de son aggrégat (par ex: son dernier S3 en date)
                    formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date(
                        sems_final
                    )

                    # Ajout (si nécessaire) et récupération du RCS associé
                    rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
                    if rcs_id not in self.trajectoires:
                        self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire(
                            nom_rcs, formsemestre_final
                        )
                    rcs = self.trajectoires[rcs_id]

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

                    # Ajout des semestres au RCS
                    rcs.add_semestres(semestres_a_aggreger)

                    # Mémorise le RCS suivi par l'étudiant
                    self.trajectoires_suivies[etudid][nom_rcs] = rcs
                    self.etudiants.trajectoires[etudid][nom_rcs] = rcs

    def cree_semxs(self):
        """Créé les SemXs (trajectoires/combinaisons de semestre de même rang x),
        en ne conservant dans les trajectoires que les regroupements
        de type Sx"""
        self.semXs = {}
        for rcs_id, trajectoire in self.trajectoires.items():
            if trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES:
                self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire)

        # L'association (pour chaque étudiant entre chaque Sx et le SemX associé)
        self.semXs_suivis = {}
        for etudid in self.etudiants.trajectoires:
            self.semXs_suivis[etudid] = {
                agregat: None for agregat in pe_rcs.TOUS_LES_SEMESTRES
            }
            for agregat in pe_rcs.TOUS_LES_SEMESTRES:
                trajectoire = self.etudiants.trajectoires[etudid][agregat]
                if trajectoire:
                    rcs_id = trajectoire.rcs_id
                    semX = self.semXs[rcs_id]
                    self.semXs_suivis[etudid][agregat] = semX
                    self.etudiants.semXs[etudid][agregat] = semX

    def cree_rcsemxs(self, options={"moyennes_ues_rcues": True}):
        """Créé tous les RCSemXs, au regard du cursus des étudiants
        analysés (trajectoires traduisant son parcours dans les
        différents semestres) + les mémorise dans les données de l'étudiant
        """
        self.rcsemxs_suivis = {}
        self.rcsemxs = {}

        if "moyennes_ues_rcues" in options and options["moyennes_ues_rcues"] == False:
            # Pas de RCSemX généré
            pe_affichage.pe_print("⚠️ Pas de RCSemX générés")
            return

        # Pour tous les étudiants du jury
        pas_de_semestres = []
        for etudid in self.trajectoires_suivies:
            self.rcsemxs_suivis[etudid] = {
                nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
            }

            # Pour chaque aggréggat de type xA ou Sx ou xS
            for agregat in pe_rcs.TOUS_LES_RCS:
                trajectoire = self.trajectoires_suivies[etudid][agregat]
                if not trajectoire:
                    self.rcsemxs_suivis[etudid][agregat] = None
                else:
                    # Identifiant de la trajectoire => donnera ceux du RCSemX
                    tid = trajectoire.rcs_id
                    # Ajout du RCSemX
                    if tid not in self.rcsemxs:
                        self.rcsemxs[tid] = pe_rcsemx.RCSemX(
                            trajectoire.nom, trajectoire.formsemestre_final
                        )

                    # Récupére les SemX (RC de type Sx) associés aux semestres de son cursus
                    # Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..

                    # Les Sx pris en compte dans l'aggrégat
                    noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"]

                    semxs_a_aggreger = {}
                    for Sx in noms_sems_aggregat:
                        semestres_etudiants = self.etudiants.cursus[etudid][Sx]
                        if not semestres_etudiants:
                            pas_de_semestres += [
                                f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}"
                            ]
                        else:
                            semx_id = get_semx_from_semestres_aggreges(
                                self.semXs, semestres_etudiants
                            )
                            if not semx_id:
                                raise (
                                    "Il manque un SemX pour créer les RCSemX dans cree_rcsemxs"
                                )
                            # Les SemX à ajouter au RCSemX
                            semxs_a_aggreger[semx_id] = self.semXs[semx_id]

                    # Ajout des SemX à ceux à aggréger dans le RCSemX
                    rcsemx = self.rcsemxs[tid]
                    rcsemx.add_semXs(semxs_a_aggreger)

                    # Mémoire du RCSemX aux informations de suivi de l'étudiant
                    self.rcsemxs_suivis[etudid][agregat] = rcsemx
                    self.etudiants.rcsemXs[etudid][agregat] = rcsemx

                    # Affichage des étudiants pour lesquels il manque un semestre
        pas_de_semestres = sorted(set(pas_de_semestres))
        if pas_de_semestres:
            pe_affichage.pe_print("⚠️ Semestres manquants :")
            pe_affichage.pe_print(
                "\n".join([" " * 10 + psd for psd in pas_de_semestres])
            )


def get_rcs_etudiant(
    semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
) -> dict[int, FormSemestre]:
    """Ensemble des semestres parcourus (trajectoire)
    par un étudiant dans le cadre
    d'un RCS de type Sx, iA ou iS et ayant pour semestre terminal `formsemestre_final`.

    Par ex: pour un RCS "3S", dont le formsemestre_terminal est un S3, regroupe
    le ou les S1 qu'il a suivi (1 ou 2 si redoublement) + le ou les S2 + le ou les S3.

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

    Args:
        cursus: Dictionnaire {fid: Formsemestre} 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 pe_rcs.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_semx_from_semestres_aggreges(
    semXs: dict[(str, int) : pe_trajectoires.SemX],
    semestres_a_aggreger: dict[(str, int):FormSemestre],
) -> (str, int):
    """Partant d'un dictionnaire de SemX (de la forme
    ``{ (nom_rcs, fid): SemX }, et connaissant une liste
    de (form)semestres suivis, renvoie l'identifiant
    (nom_rcs, fid) du SemX qui lui correspond.

    Le SemX qui correspond est tel que :

    * le semestre final du SemX correspond au dernier semestre en date des
      semestres_a_aggreger
    * le rang du SemX est le même que celui des semestres_aggreges
    * les semestres_a_aggreger (plus large, car contenant plusieurs
       parcours), matchent avec les semestres aggrégés
      par le SemX


    Returns:
        rcf_id: L'identifiant du RCF trouvé
    """
    assert semestres_a_aggreger, "Pas de semestres à aggréger"
    rangs_a_aggreger = [sem.semestre_id for fid, sem in semestres_a_aggreger.items()]
    assert (
        len(set(rangs_a_aggreger)) == 1
    ), "Tous les sem à aggréger doivent être de même rang"

    # Le dernier semestre des semestres à regrouper
    dernier_sem_a_aggreger = pe_comp.get_dernier_semestre_en_date(semestres_a_aggreger)

    semxs_ids = []  # Au cas où il y ait plusieurs solutions
    for semx_id, semx in semXs.items():
        # Même semestre final ?
        if semx.get_formsemestre_id_final() == dernier_sem_a_aggreger.formsemestre_id:
            # Les fids
            fids_a_aggreger = set(semestres_a_aggreger.keys())
            # Ceux du semx
            fids_semx = set(semx.semestres_aggreges.keys())
            if fids_a_aggreger.issubset(
                fids_semx
            ):  # tous les semestres du semx correspond à des sems de la trajectoire
                semxs_ids += [semx_id]
    if len(semxs_ids) == 0:
        return None  # rien trouvé
    elif len(semxs_ids) == 1:
        return semxs_ids[0]
    else:
        raise "Plusieurs solutions :)"