"""ScoDoc 9 models : Modules
"""

from app import db
from app.models import APO_CODE_STR_LEN
from app.scodoc import sco_utils as scu
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType


class Module(db.Model):
    """Module"""

    __tablename__ = "notes_modules"

    id = db.Column(db.Integer, primary_key=True)
    module_id = db.synonym("id")
    titre = db.Column(db.Text())
    abbrev = db.Column(db.Text())  # nom court
    # certains départements ont des codes infiniment longs: donc Text !
    code = db.Column(db.Text(), nullable=False)
    heures_cours = db.Column(db.Float)
    heures_td = db.Column(db.Float)
    heures_tp = db.Column(db.Float)
    coefficient = db.Column(db.Float)  # coef PPN (sauf en APC)
    ects = db.Column(db.Float)  # Crédits ECTS
    ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), index=True)
    formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
    matiere_id = db.Column(db.Integer, db.ForeignKey("notes_matieres.id"))
    # pas un id mais le numéro du semestre: 1, 2, ...
    # note: en APC, le semestre qui fait autorité est celui de l'UE
    semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
    numero = db.Column(db.Integer)  # ordre de présentation
    # id de l'element pedagogique Apogee correspondant:
    code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
    # Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
    module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    # Relations:
    modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
    ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
    tags = db.relationship(
        "NotesTag",
        secondary="notes_modules_tags",
        lazy=True,
        backref=db.backref("modules", lazy=True),
    )

    def __init__(self, **kwargs):
        self.ue_coefs = []
        super(Module, self).__init__(**kwargs)

    def __repr__(self):
        return f"<Module{ModuleType(self.module_type or ModuleType.STANDARD).name} id={self.id} code={self.code}>"

    def to_dict(self):
        e = dict(self.__dict__)
        e.pop("_sa_instance_state", None)
        # ScoDoc7 output_formators: (backward compat)
        e["module_id"] = self.id
        e["heures_cours"] = 0.0 if self.heures_cours is None else self.heures_cours
        e["heures_td"] = 0.0 if self.heures_td is None else self.heures_td
        e["heures_tp"] = 0.0 if self.heures_tp is None else self.heures_tp
        e["numero"] = 0 if self.numero is None else self.numero
        e["coefficient"] = 0.0 if self.coefficient is None else self.coefficient
        e["module_type"] = 0 if self.module_type is None else self.module_type
        e["code_apogee"] = e["code_apogee"] or ""  # pas de None
        return e

    def is_apc(self):
        "True si module SAÉ ou Ressource"
        return self.module_type and scu.ModuleType(self.module_type) in {
            scu.ModuleType.RESSOURCE,
            scu.ModuleType.SAE,
        }

    def type_name(self):
        return scu.MODULE_TYPE_NAMES[self.module_type]

    def set_ue_coef(self, ue, coef: float) -> None:
        """Set coef module vers cette UE"""
        self.update_ue_coef_dict({ue.id: coef})

    def set_ue_coef_dict(self, ue_coef_dict: dict) -> None:
        """set coefs vers les UE (remplace existants)
        ue_coef_dict = { ue_id : coef }
        Les coefs nuls (zéro) ne sont pas stockés: la relation est supprimée.
        """
        changed = False
        for ue_id, coef in ue_coef_dict.items():
            # Existant ?
            coefs = [c for c in self.ue_coefs if c.ue_id == ue_id]
            if coefs:
                ue_coef = coefs[0]
                if coef == 0.0:  # supprime ce coef
                    db.session.delete(ue_coef)
                    changed = True
                elif coef != ue_coef.coef:
                    ue_coef.coef = coef
                    db.session.add(ue_coef)
                    changed = True
            else:
                # crée nouveau coef:
                if coef != 0.0:
                    ue = UniteEns.query.get(ue_id)
                    ue_coef = ModuleUECoef(module=self, ue=ue, coef=coef)
                    self.ue_coefs.append(ue_coef)
                    changed = True
        if changed:
            self.formation.invalidate_module_coefs()

    def update_ue_coef_dict(self, ue_coef_dict: dict):
        """update coefs vers UE (ajoute aux existants)"""
        current = self.get_ue_coef_dict()
        current.update(ue_coef_dict)
        self.set_ue_coef_dict(current)

    def get_ue_coef_dict(self):
        """returns { ue_id : coef }"""
        return {p.ue.id: p.coef for p in self.ue_coefs}

    def delete_ue_coef(self, ue):
        """delete coef"""
        ue_coef = ModuleUECoef.query.get((self.id, ue.id))
        if ue_coef:
            db.session.delete(ue_coef)
            self.formation.invalidate_module_coefs()

    def get_ue_coefs_sorted(self):
        "les coefs d'UE, trié par numéro d'UE"
        # je n'ai pas su mettre un order_by sur le backref sans avoir
        # à redéfinir les relationships...
        return sorted(self.ue_coefs, key=lambda x: x.ue.numero)

    def ue_coefs_list(self, include_zeros=True):
        """Liste des coefs vers les UE (pour les modules APC).
        Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre,
        sauf UE bonus sport.
        Result: List of tuples [ (ue, coef) ]
        """
        if not self.is_apc():
            return []
        if include_zeros:
            # Toutes les UE du même semestre:
            ues_semestre = (
                self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
                .filter(UniteEns.type != UE_SPORT)
                .order_by(UniteEns.numero)
                .all()
            )
            coefs_dict = self.get_ue_coef_dict()
            coefs_list = []
            for ue in ues_semestre:
                coefs_list.append((ue, coefs_dict.get(ue.id, 0.0)))
            return coefs_list
        # Liste seulement les coefs définis:
        return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]


class ModuleUECoef(db.Model):
    """Coefficients des modules vers les UE (APC, BUT)
    En mode APC, ces coefs remplacent le coefficient "PPN" du module.
    """

    __tablename__ = "module_ue_coef"

    module_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_modules.id", ondelete="CASCADE"),
        primary_key=True,
    )
    ue_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_ue.id", ondelete="CASCADE"),
        primary_key=True,
    )
    coef = db.Column(
        db.Float,
        nullable=False,
    )
    module = db.relationship(
        Module,
        backref=db.backref(
            "ue_coefs",
            passive_deletes=True,
            cascade="save-update, merge, delete, delete-orphan",
        ),
    )
    ue = db.relationship(
        "UniteEns",
        backref=db.backref(
            "module_ue_coefs",
            passive_deletes=True,
            cascade="save-update, merge, delete, delete-orphan",
        ),
    )


class NotesTag(db.Model):
    """Tag sur un module"""

    __tablename__ = "notes_tags"
    __table_args__ = (db.UniqueConstraint("title", "dept_id"),)

    id = db.Column(db.Integer, primary_key=True)
    tag_id = db.synonym("id")

    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
    title = db.Column(db.Text(), nullable=False)


# Association tag <-> module
notes_modules_tags = db.Table(
    "notes_modules_tags",
    db.Column(
        "tag_id",
        db.Integer,
        db.ForeignKey("notes_tags.id", ondelete="CASCADE"),
    ),
    db.Column(
        "module_id", db.Integer, db.ForeignKey("notes_modules.id", ondelete="CASCADE")
    ),
)

from app.models.ues import UniteEns