"""ScoDoc 9 models : Formations
"""

from app import db
from app.comp import df_cache
from app.models import SHORT_STR_LEN
from app.scodoc import notesdb as ndb
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_utils as scu


class Formation(db.Model):
    """Programme pédagogique d'une formation"""

    __tablename__ = "notes_formations"
    __table_args__ = (db.UniqueConstraint("dept_id", "acronyme", "titre", "version"),)

    id = db.Column(db.Integer, primary_key=True)
    formation_id = db.synonym("id")
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)

    acronyme = db.Column(db.Text(), nullable=False)
    titre = db.Column(db.Text(), nullable=False)
    titre_officiel = db.Column(db.Text(), nullable=False)
    version = db.Column(db.Integer, default=1, server_default="1")
    formation_code = db.Column(
        db.String(SHORT_STR_LEN),
        server_default=db.text("notes_newid_fcod()"),
        nullable=False,
    )
    # nb: la fonction SQL notes_newid_fcod doit être créée à part
    type_parcours = db.Column(db.Integer, default=0, server_default="0")
    code_specialite = db.Column(db.String(SHORT_STR_LEN))

    # Optionnel, pour les formations type BUT
    referentiel_competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id")
    )
    ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
    formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
    ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")
    modules = db.relationship("Module", lazy="dynamic", backref="formation")

    def __repr__(self):
        return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"

    def to_dict(self):
        e = dict(self.__dict__)
        e.pop("_sa_instance_state", None)
        # ScoDoc7 output_formators: (backward compat)
        e["formation_id"] = self.id
        return e

    def get_parcours(self):
        """get l'instance de TypeParcours de cette formation"""
        return sco_codes_parcours.get_parcours_from_code(self.type_parcours)

    def is_apc(self):
        "True si formation APC avec SAE (BUT)"
        return self.get_parcours().APC_SAE

    def get_module_coefs(self, semestre_idx: int = None):
        """Les coefs des modules vers les UE (accès via cache)"""
        from app.comp import moy_ue

        if semestre_idx is None:
            key = f"{self.id}"
        else:
            key = f"{self.id}.{semestre_idx}"

        modules_coefficients = df_cache.ModuleCoefsCache.get(key)
        if modules_coefficients is None:
            modules_coefficients, _, _ = moy_ue.df_load_module_coefs(
                self.id, semestre_idx
            )
            df_cache.ModuleCoefsCache.set(key, modules_coefficients)
        return modules_coefficients

    def has_locked_sems(self):
        "True if there is a locked formsemestre in this formation"
        return len(self.formsemestres.filter_by(etat=False).all()) > 0

    def invalidate_module_coefs(self, semestre_idx: int = None):
        """Invalide les coefficients de modules cachés.
        Si semestre_idx est None, invalide tous les semestres,
        sinon invalide le semestre indiqué et le cache de la formation.
        """
        if semestre_idx is None:
            keys = {f"{self.id}.{m.semestre_id}" for m in self.modules}
        else:
            keys = f"{self.id}.{semestre_idx}"
        df_cache.ModuleCoefsCache.delete_many(keys | {f"{self.id}"})
        sco_cache.invalidate_formsemestre()

    def invalidate_cached_sems(self):
        for sem in self.formsemestres:
            sco_cache.invalidate_formsemestre(formsemestre_id=sem.id)

    def force_semestre_modules_aux_ues(self) -> None:
        """
        Affecte à chaque module de cette formation le semestre de son UE de rattachement,
        si elle en a une.
        Devrait être appelé lorsqu'on change le type de formation vers le BUT, et aussi
        lorsqu'on change le semestre d'une UE BUT.
        Utile pour la migration des anciennes formations vers le BUT.
        Invalide les caches coefs/poids.
        """
        if not self.is_apc():
            return
        change = False
        for mod in self.modules:
            if (
                mod.ue.semestre_idx is not None
                and mod.ue.semestre_idx > 0
                and mod.semestre_id != mod.ue.semestre_idx
            ):
                mod.semestre_id = mod.ue.semestre_idx
                db.session.add(mod)
                change = True
        db.session.commit()
        if change:
            self.invalidate_module_coefs()


class Matiere(db.Model):
    """Matières: regroupe les modules d'une UE
    La matière a peu d'utilité en dehors de la présentation des modules
    d'une UE.
    """

    __tablename__ = "notes_matieres"
    __table_args__ = (db.UniqueConstraint("ue_id", "titre"),)

    id = db.Column(db.Integer, primary_key=True)
    matiere_id = db.synonym("id")
    ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"))
    titre = db.Column(db.Text())
    numero = db.Column(db.Integer)  # ordre de présentation

    modules = db.relationship("Module", lazy="dynamic", backref="matiere")