# -*- coding: UTF-8 -*

"""Modèles base de données ScoDoc
"""

import sqlalchemy
from app import db

CODE_STR_LEN = 16  # chaine pour les codes
SHORT_STR_LEN = 32  # courtes chaine, eg acronymes
APO_CODE_STR_LEN = 512  # nb de car max d'un code Apogée (il peut y en avoir plusieurs)
GROUPNAME_STR_LEN = 64
USERNAME_STR_LEN = 64

convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

metadata_obj = sqlalchemy.MetaData(naming_convention=convention)


class ScoDocModel(db.Model):
    """Superclass for our models. Add some useful methods for editing, cloning, etc.
    - clone() : clone object and add copy to session, do not commit.
    - create_from_dict() : create instance from given dict, applying conversions.
    - convert_dict_fields() : convert dict values, called before instance creation.
        By default, do nothing.
    - from_dict() : update object using data from dict. data is first converted.
    - edit() : update from wtf form.
    """

    __abstract__ = True  # declare an abstract class for SQLAlchemy

    def clone(self, not_copying=()):
        """Clone, not copying the given attrs
        Attention: la copie n'a pas d'id avant le prochain flush ou commit.
        """
        d = dict(self.__dict__)
        d.pop("id", None)  # get rid of id
        d.pop("_sa_instance_state", None)  # get rid of SQLAlchemy special attr
        for k in not_copying:
            d.pop(k, None)
        copy = self.__class__(**d)
        db.session.add(copy)
        return copy

    @classmethod
    def create_from_dict(cls, data: dict) -> "ScoDocModel":
        """Create a new instance of the model with attributes given in dict.
        The instance is added to the session (but not flushed nor committed).
        Use only relevant attributes for the given model and ignore others.
        """
        if data:
            args = cls.convert_dict_fields(cls.filter_model_attributes(data))
            if args:
                obj = cls(**args)
            else:
                obj = cls()
        else:
            obj = cls()
        db.session.add(obj)
        return obj

    @classmethod
    def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
        """Returns a copy of dict with only the keys belonging to the Model and not in excluded.
        Add 'id' to excluded."""
        excluded = excluded or set()
        excluded.add("id")  # always exclude id
        # Les attributs du modèle qui sont des variables: (élimine les __ et les alias comme adm_id)
        my_attributes = [
            a
            for a in dir(cls)
            if isinstance(
                getattr(cls, a), sqlalchemy.orm.attributes.InstrumentedAttribute
            )
        ]
        # Filtre les arguments utiles
        return {
            k: v for k, v in data.items() if k in my_attributes and k not in excluded
        }

    @classmethod
    def convert_dict_fields(cls, args: dict) -> dict:
        """Convert fields from the given dict to model's attributes values. No side effect.
        By default, do nothing, but is overloaded by some subclasses.
        args: dict with args in application.
        returns: dict to store in model's db.
        """
        # virtual, by default, do nothing
        return args

    def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool:
        """Update object's fields given in dict. Add to session but don't commit.
        True if modification.
        """
        args_dict = self.convert_dict_fields(
            self.filter_model_attributes(args, excluded=excluded)
        )
        modified = False
        for key, value in args_dict.items():
            if hasattr(self, key) and value != getattr(self, key):
                setattr(self, key, value)
                modified = True
        db.session.add(self)
        return modified

    def edit_from_form(self, form) -> bool:
        """Generic edit method for updating model instance.
        True if modification.
        """
        args = {field.name: field.data for field in form}
        return self.from_dict(args)


from app.models.absences import Absence, AbsenceNotification, BilletAbsence
from app.models.departements import Departement
from app.models.etudiants import (
    Identite,
    Adresse,
    Admission,
    ItemSuivi,
    ItemSuiviTag,
    itemsuivi_tags_assoc,
    EtudAnnotation,
)
from app.models.events import Scolog, ScolarNews
from app.models.formations import Formation, Matiere
from app.models.modules import Module, ModuleUECoef, NotesTag, notes_modules_tags
from app.models.ues import DispenseUE, UniteEns
from app.models.formsemestre import (
    FormSemestre,
    FormSemestreEtape,
    FormationModalite,
    FormSemestreUECoef,
    FormSemestreUEComputationExpr,
    FormSemestreCustomMenu,
    FormSemestreInscription,
    notes_formsemestre_responsables,
    NotesSemSet,
    notes_semset_formsemestre,
)
from app.models.moduleimpls import (
    ModuleImpl,
    notes_modules_enseignants,
    ModuleImplInscription,
)
from app.models.evaluations import (
    Evaluation,
    EvaluationUEPoids,
)
from app.models.groups import Partition, GroupDescr, group_membership
from app.models.notes import (
    BulAppreciations,
    NotesNotes,
    NotesNotesLog,
)
from app.models.validations import (
    ScolarFormSemestreValidation,
    ScolarAutorisationInscription,
)
from app.models.preferences import ScoPreference

from app.models.but_refcomp import (
    ApcAppCritique,
    ApcCompetence,
    ApcNiveau,
    ApcParcours,
    ApcReferentielCompetences,
    ApcSituationPro,
)
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE

from app.models.config import ScoDocSiteConfig

from app.models.assiduites import Assiduite, Justificatif
from app.models.scolar_event import ScolarEvent
