# -*- coding: UTF-8 -*
"""ScoDoc models: moduleimpls
"""
import pandas as pd
import flask_sqlalchemy

from app import db
from app.comp import df_cache
from app.models.etudiants import Identite
from app.models.modules import Module

import app.scodoc.notesdb as ndb
from app.scodoc import sco_utils as scu


class ModuleImpl(db.Model):
    """Mise en oeuvre d'un module pour une annee/semestre"""

    __tablename__ = "notes_moduleimpl"
    __table_args__ = (db.UniqueConstraint("formsemestre_id", "module_id"),)

    id = db.Column(db.Integer, primary_key=True)
    moduleimpl_id = db.synonym("id")
    module_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_modules.id"),
    )
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
        index=True,
    )
    responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
    # formule de calcul moyenne:
    computation_expr = db.Column(db.Text())

    evaluations = db.relationship("Evaluation", lazy="dynamic", backref="moduleimpl")
    enseignants = db.relationship(
        "User",
        secondary="notes_modules_enseignants",
        lazy="dynamic",
        backref="moduleimpl",
        viewonly=True,
    )

    def __init__(self, **kwargs):
        super(ModuleImpl, self).__init__(**kwargs)

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>"

    def get_evaluations_poids(self) -> pd.DataFrame:
        """Les poids des évaluations vers les UE (accès via cache)"""
        evaluations_poids = df_cache.EvaluationsPoidsCache.get(self.id)
        if evaluations_poids is None:
            from app.comp import moy_mod

            evaluations_poids, _ = moy_mod.load_evaluations_poids(self.id)
            df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
        return evaluations_poids

    def invalidate_evaluations_poids(self):
        """Invalide poids cachés"""
        df_cache.EvaluationsPoidsCache.delete(self.id)

    def check_apc_conformity(self) -> bool:
        """true si les poids des évaluations du module permettent de satisfaire
        les coefficients du PN.
        """
        if not self.module.formation.get_parcours().APC_SAE or (
            self.module.module_type != scu.ModuleType.RESSOURCE
            and self.module.module_type != scu.ModuleType.SAE
        ):
            return True
        from app.comp import moy_mod

        return moy_mod.moduleimpl_is_conforme(
            self,
            self.get_evaluations_poids(),
            self.module.formation.get_module_coefs(self.module.semestre_id),
        )

    def to_dict(self):
        """as a dict, with the same conversions as in ScoDoc7, including module"""
        e = dict(self.__dict__)
        e.pop("_sa_instance_state", None)
        # ScoDoc7 output_formators: (backward compat)
        e["moduleimpl_id"] = self.id
        e["ens"] = [
            {"moduleimpl_id": self.id, "ens_id": e.id} for e in self.enseignants
        ]
        e["module"] = self.module.to_dict()
        return e


# Enseignants (chargés de TD ou TP) d'un moduleimpl
notes_modules_enseignants = db.Table(
    "notes_modules_enseignants",
    db.Column(
        "moduleimpl_id",
        db.Integer,
        db.ForeignKey("notes_moduleimpl.id"),
    ),
    db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
    # ? db.UniqueConstraint("moduleimpl_id", "ens_id"),
)
# XXX il manque probablement une relation pour gérer cela


class ModuleImplInscription(db.Model):
    """Inscription à un module  (etudiants,moduleimpl)"""

    __tablename__ = "notes_moduleimpl_inscription"
    __table_args__ = (db.UniqueConstraint("moduleimpl_id", "etudid"),)

    id = db.Column(db.Integer, primary_key=True)
    moduleimpl_inscription_id = db.synonym("id")
    moduleimpl_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_moduleimpl.id"),
        index=True,
    )
    etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
    etud = db.relationship(
        Identite,
        backref=db.backref("moduleimpl_inscriptions", cascade="all, delete-orphan"),
    )
    modimpl = db.relationship(
        ModuleImpl,
        backref=db.backref("inscriptions", cascade="all, delete-orphan"),
    )

    @classmethod
    def etud_modimpls_in_ue(
        cls, formsemestre_id: int, etudid: int, ue_id: int
    ) -> flask_sqlalchemy.BaseQuery:
        """moduleimpls de l'UE auxquels l'étudiant est inscrit.
        (Attention: inutile en APC, il faut considérer les coefficients)
        """
        return ModuleImplInscription.query.filter(
            ModuleImplInscription.etudid == etudid,
            ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
            ModuleImpl.formsemestre_id == formsemestre_id,
            ModuleImpl.module_id == Module.id,
            Module.ue_id == ue_id,
        )

    @classmethod
    def nb_inscriptions_dans_ue(
        cls, formsemestre_id: int, etudid: int, ue_id: int
    ) -> int:
        """Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
        return cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id).count()

    @classmethod
    def sum_coefs_modimpl_ue(
        cls, formsemestre_id: int, etudid: int, ue_id: int
    ) -> float:
        """Somme des coefficients des modules auxquels l'étudiant est inscrit
        dans l'UE du semestre indiqué.
        N'utilise que les coefficients, donc inadapté aux formations APC.
        """
        return sum(
            [
                inscr.modimpl.module.coefficient
                for inscr in cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id)
            ]
        )