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

"""Cursus en BUT

Classe raccordant avec ScoDoc 7:
 ScoDoc 7 utilisait sco_cursus_dut.SituationEtudCursus

 Ce module définit une classe SituationEtudCursusBUT
 avec la même interface.

"""
import collections
from typing import Union

from flask import g, url_for

from app import db
from app import log
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat

from app.comp import res_sem
from app.models import formsemestre

from app.models.but_refcomp import (
    ApcAnneeParcours,
    ApcCompetence,
    ApcNiveau,
    ApcParcours,
    ApcParcoursNiveauCompetence,
)
from app.models import Scolog, ScolarAutorisationInscription
from app.models.but_validations import (
    ApcValidationAnnee,
    ApcValidationRCUE,
    RegroupementCoherentUE,
)
from app.models.etudiants import Identite
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre, FormSemestreInscription
from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus as sco_codes
from app.scodoc.codes_cursus import RED, UE_STANDARD
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError

from app.scodoc import sco_cursus_dut


class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
    """Pour compat ScoDoc 7: à revoir pour le BUT"""

    def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
        super().__init__(etud, formsemestre_id, res)
        # Ajustements pour le BUT
        self.can_compensate_with_prev = False  # jamais de compensation à la mode DUT

    def check_compensation_dut(self, semc: dict, ntc: NotesTableCompat):
        "Jamais de compensation façon DUT"
        return False

    def parcours_validated(self):
        "True si le parcours est validé"
        return False  # XXX TODO


class EtudCursusBUT:
    """L'état de l'étudiant dans son cursus BUT
    Liste des niveaux validés/à valider
    """

    def __init__(self, etud: Identite, formation: Formation):
        """formation indique la spécialité préparée"""
        # Vérifie que l'étudiant est bien inscrit à un sem. de cette formation
        if formation.id not in (
            ins.formsemestre.formation.id for ins in etud.formsemestre_inscriptions
        ):
            raise ScoValueError(
                f"{etud.nomprenom} non inscrit dans {formation.titre} v{formation.version}"
            )
        if not formation.referentiel_competence:
            raise ScoNoReferentielCompetences(formation=formation)
        #
        self.etud = etud
        self.formation = formation
        self.inscriptions = sorted(
            [
                ins
                for ins in etud.formsemestre_inscriptions
                if ins.formsemestre.formation.referentiel_competence
                and (
                    ins.formsemestre.formation.referentiel_competence.id
                    == formation.referentiel_competence.id
                )
            ],
            key=lambda s: (s.formsemestre.semestre_id, s.formsemestre.date_debut),
        )
        "Liste des inscriptions aux sem. de la formation, triées par indice et chronologie"
        self.parcour: ApcParcours = self.inscriptions[-1].parcour
        "Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
        self.niveaux_by_annee = {}
        "{ annee : liste des niveaux à valider }"
        self.niveaux: dict[int, ApcNiveau] = {}
        "cache les niveaux"
        for annee in (1, 2, 3):
            niveaux_d = formation.referentiel_competence.get_niveaux_by_parcours(
                annee, self.parcour
            )[1]
            # groupe les niveaux de tronc commun et ceux spécifiques au parcour
            self.niveaux_by_annee[annee] = niveaux_d["TC"] + (
                niveaux_d[self.parcour.id] if self.parcour else []
            )
            self.niveaux.update(
                {niveau.id: niveau for niveau in self.niveaux_by_annee[annee]}
            )
        # Probablement inutile:
        # # Cherche les validations de jury enregistrées pour chaque niveau
        # self.validations_by_niveau = collections.defaultdict(lambda: [])
        # " { niveau_id : [ ApcValidationRCUE ] }"
        # for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
        #     self.validations_by_niveau[validation_rcue.niveau().id].append(
        #         validation_rcue
        #     )
        # self.validation_by_niveau = {
        #     niveau_id: sorted(
        #         validations, key=lambda v: sco_codes.BUT_CODES_ORDERED[v.code]
        #     )[0]
        #     for niveau_id, validations in self.validations_by_niveau.items()
        # }
        # "{ niveau_id : meilleure validation pour ce niveau }"

        self.validation_par_competence_et_annee = {}
        """{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
        for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
            niveau = validation_rcue.niveau()
            if not niveau.competence.id in self.validation_par_competence_et_annee:
                self.validation_par_competence_et_annee[niveau.competence.id] = {}
            previous_validation = self.validation_par_competence_et_annee.get(
                niveau.competence.id
            ).get(validation_rcue.annee())
            # prend la "meilleure" validation
            if (not previous_validation) or (
                sco_codes.BUT_CODES_ORDERED[validation_rcue.code]
                > sco_codes.BUT_CODES_ORDERED[previous_validation["code"]]
            ):
                self.validation_par_competence_et_annee[niveau.competence.id][
                    niveau.annee
                ] = validation_rcue

        self.competences = {
            competence.id: competence
            for competence in (
                self.parcour.query_competences()
                if self.parcour
                else self.formation.referentiel_competence.get_competences_tronc_commun()
            )
        }
        "cache { competence_id : competence }"

    def to_dict(self):
        """
        {
            competence_id : {
                annee : meilleure_validation
            }
        }
        """
        return {
            competence.id: {
                annee: self.validation_par_competence_et_annee.get(
                    competence.id, {}
                ).get(annee)
                for annee in ("BUT1", "BUT2", "BUT3")
            }
            for competence in self.competences.values()
        }

    # XXX TODO OPTIMISATION ACCESS TABLE JURY
    def to_dict_codes(self) -> dict[int, dict[str, int]]:
        """
        {
            competence_id : {
                annee : { validation}
            }
        }
        où validation est un petit dict avec niveau_id, etc.
        """
        d = {}
        for competence in self.competences.values():
            d[competence.id] = {}
            for annee in ("BUT1", "BUT2", "BUT3"):
                validation_rcue: ApcValidationRCUE = (
                    self.validation_par_competence_et_annee.get(competence.id, {}).get(
                        annee
                    )
                )

                d[competence.id][annee] = (
                    validation_rcue.to_dict_codes() if validation_rcue else None
                )
        return d