"""ScoDoc8 models : Formations (hors BUT)
"""
from typing import Any

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


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))

    ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
    formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
    ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")

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

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


class UniteEns(db.Model):
    """Unité d'Enseignement (UE)"""

    __tablename__ = "notes_ue"

    id = db.Column(db.Integer, primary_key=True)
    ue_id = db.synonym("id")
    formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
    acronyme = db.Column(db.Text(), nullable=False)
    numero = db.Column(db.Integer)  # ordre de présentation
    titre = db.Column(db.Text())
    # Le semestre_idx n'est pas un id mais le numéro du semestre: 1, 2, ...
    # En ScoDoc7 et pour les formations classiques, il est NULL
    # (le numéro du semestre étant alors déterminé par celui des modules de l'UE)
    # Pour les formations APC, il est obligatoire (de 1 à 6 pour le BUT):
    semestre_idx = db.Column(db.Integer, nullable=True, index=True)
    # Type d'UE: 0 normal ("fondamentale"), 1 "sport", 2 "projet et stage (LP)",
    # 4 "élective"
    type = db.Column(db.Integer, default=0, server_default="0")
    # Les UE sont "compatibles" (pour la capitalisation) ssi elles ont ^m code
    # note: la fonction SQL notes_newid_ucod doit être créée à part
    ue_code = db.Column(
        db.String(SHORT_STR_LEN),
        server_default=db.text("notes_newid_ucod()"),
        nullable=False,
    )
    ects = db.Column(db.Float)  # nombre de credits ECTS
    is_external = db.Column(db.Boolean(), default=False, server_default="false")
    # id de l'element pedagogique Apogee correspondant:
    code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
    # coef UE, utilise seulement si l'option use_ue_coefs est activée:
    coefficient = db.Column(db.Float)

    # relations
    matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
    modules = db.relationship("Module", lazy="dynamic", backref="ue")

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


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")


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, ...
    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)
    # Relations:
    modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
    ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)

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

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

    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.
        """
        ue_coefs = []
        for ue_id, coef in ue_coef_dict.items():
            ue = UniteEns.query.get(ue_id)
            if coef == 0.0:
                self.delete_ue_coef(ue)
            else:
                ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef))
        self.ue_coefs = ue_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)

    def ue_coefs_descr(self):
        """List of tuples [ (ue_acronyme, coef) ]"""
        return [(c.ue.acronyme, c.coef) for c in self.ue_coefs]


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"), primary_key=True
    )
    ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True)
    coef = db.Column(
        db.Float,
        nullable=False,
    )
    module = db.relationship(
        Module,
        backref=db.backref("ue_coefs", cascade="all, delete-orphan"),
    )
    ue = db.relationship(
        UniteEns,
        backref=db.backref("module_ue_coefs", cascade="all, 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")
    ),
)
