##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
"""
from datetime import datetime

from sqlalchemy.orm import class_mapper
import sqlalchemy

from app import db

from app.scodoc.sco_utils import ModuleType


# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
def attribute_names(cls):
    "liste ids (noms de colonnes) d'un modèle"
    return [
        prop.key
        for prop in class_mapper(cls).iterate_properties
        if isinstance(prop, sqlalchemy.orm.ColumnProperty)
    ]


class XMLModel:
    """Mixin class, to ease loading Orebut XMLs"""

    _xml_attribs = {}  # to be overloaded
    id = "_"

    @classmethod
    def attr_from_xml(cls, args: dict) -> dict:
        """dict with attributes imported from Orébut XML
        and renamed for our models.
        The mapping is specified by the _xml_attribs
        attribute in each model class.
        Keep only attributes corresponding to columns in our model:
        other XML attributes are simply ignored.
        """
        columns = attribute_names(cls)
        renamed_attributes = {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}
        return {k: renamed_attributes[k] for k in renamed_attributes if k in columns}

    def __repr__(self):
        return f'<{self.__class__.__name__} {self.id} "{self.titre if hasattr(self, "titre") else ""}">'


class ApcReferentielCompetences(db.Model, XMLModel):
    "Référentiel de compétence d'une spécialité"
    id = db.Column(db.Integer, primary_key=True)
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
    annexe = db.Column(db.Text())
    specialite = db.Column(db.Text())
    specialite_long = db.Column(db.Text())
    type_titre = db.Column(db.Text())
    type_structure = db.Column(db.Text())
    type_departement = db.Column(db.Text())  # "secondaire", "tertiaire"
    version_orebut = db.Column(db.Text())
    _xml_attribs = {  # Orébut xml attrib : attribute
        "type": "type_titre",
        "version": "version_orebut",
    }
    # ScoDoc specific fields:
    scodoc_date_loaded = db.Column(db.DateTime, default=datetime.utcnow)
    scodoc_orig_filename = db.Column(db.Text())
    # Relations:
    competences = db.relationship(
        "ApcCompetence",
        backref="referentiel",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )
    parcours = db.relationship(
        "ApcParcours",
        backref="referentiel",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )
    formations = db.relationship("Formation", backref="referentiel_competence")

    def __repr__(self):
        return f"<ApcReferentielCompetences {self.id} {self.specialite}>"

    def to_dict(self):
        """Représentation complète du ref. de comp.
        comme un dict.
        """
        return {
            "dept_id": self.dept_id,
            "annexe": self.annexe,
            "specialite": self.specialite,
            "specialite_long": self.specialite_long,
            "type_structure": self.type_structure,
            "type_departement": self.type_departement,
            "type_titre": self.type_titre,
            "version_orebut": self.version_orebut,
            "scodoc_date_loaded": self.scodoc_date_loaded.isoformat() + "Z"
            if self.scodoc_date_loaded
            else "",
            "scodoc_orig_filename": self.scodoc_orig_filename,
            "competences": {x.titre: x.to_dict() for x in self.competences},
            "parcours": {x.code: x.to_dict() for x in self.parcours},
        }


class ApcCompetence(db.Model, XMLModel):
    "Compétence"
    id = db.Column(db.Integer, primary_key=True)
    referentiel_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
    )
    # les compétences dans Orébut sont identifiées par leur id unique
    # (mais id_orebut n'est pas unique car le même ref. pourra être chargé dans plusieurs depts)
    id_orebut = db.Column(db.Text(), nullable=True, index=True)
    titre = db.Column(db.Text(), nullable=False, index=True)
    titre_long = db.Column(db.Text())
    couleur = db.Column(db.Text())
    numero = db.Column(db.Integer)  # ordre de présentation
    _xml_attribs = {  # xml_attrib : attribute
        "id": "id_orebut",
        "nom_court": "titre",  # was name
        "libelle_long": "titre_long",
    }
    situations = db.relationship(
        "ApcSituationPro",
        backref="competence",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )
    composantes_essentielles = db.relationship(
        "ApcComposanteEssentielle",
        backref="competence",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )
    niveaux = db.relationship(
        "ApcNiveau",
        backref="competence",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )

    def __repr__(self):
        return f"<ApcCompetence {self.id} {self.titre}>"

    def to_dict(self):
        return {
            "id_orebut": self.id_orebut,
            "titre": self.titre,
            "titre_long": self.titre_long,
            "couleur": self.couleur,
            "numero": self.numero,
            "situations": [x.to_dict() for x in self.situations],
            "composantes_essentielles": [
                x.to_dict() for x in self.composantes_essentielles
            ],
            "niveaux": {x.annee: x.to_dict() for x in self.niveaux},
        }


class ApcSituationPro(db.Model, XMLModel):
    "Situation professionnelle"
    id = db.Column(db.Integer, primary_key=True)
    competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
    )
    libelle = db.Column(db.Text(), nullable=False)
    # aucun attribut (le text devient le libellé)
    def to_dict(self):
        return {"libelle": self.libelle}


class ApcComposanteEssentielle(db.Model, XMLModel):
    "Composante essentielle"
    id = db.Column(db.Integer, primary_key=True)
    competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
    )
    libelle = db.Column(db.Text(), nullable=False)

    def to_dict(self):
        return {"libelle": self.libelle}


class ApcNiveau(db.Model, XMLModel):
    id = db.Column(db.Integer, primary_key=True)
    competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
    )
    libelle = db.Column(db.Text(), nullable=False)
    annee = db.Column(db.Text(), nullable=False)  # "BUT2"
    # L'ordre est l'année d'apparition de ce niveau
    ordre = db.Column(db.Integer, nullable=False)  # 1, 2, 3
    app_critiques = db.relationship(
        "ApcAppCritique",
        backref="niveau",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )

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

    def to_dict(self):
        return {
            "libelle": self.libelle,
            "annee": self.annee,
            "ordre": self.ordre,
            "app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
        }


class ApcAppCritique(db.Model, XMLModel):
    "Apprentissage Critique BUT"
    id = db.Column(db.Integer, primary_key=True)
    niveau_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"), nullable=False)
    code = db.Column(db.Text(), nullable=False, index=True)
    libelle = db.Column(db.Text())

    modules = db.relationship(
        "Module",
        secondary="apc_modules_acs",
        lazy="dynamic",
        backref=db.backref("app_critiques", lazy="dynamic"),
    )

    def to_dict(self) -> dict:
        return {"libelle": self.libelle}

    def get_label(self) -> str:
        return self.code + " - " + self.titre

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.code}>"

    def get_saes(self):
        """Liste des SAE associées"""
        return [m for m in self.modules if m.module_type == ModuleType.SAE]


ApcAppCritiqueModules = db.Table(
    "apc_modules_acs",
    db.Column("module_id", db.ForeignKey("notes_modules.id")),
    db.Column("app_crit_id", db.ForeignKey("apc_app_critique.id")),
)


class ApcParcours(db.Model, XMLModel):
    id = db.Column(db.Integer, primary_key=True)
    referentiel_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
    )
    numero = db.Column(db.Integer)  # ordre de présentation
    code = db.Column(db.Text(), nullable=False)
    libelle = db.Column(db.Text(), nullable=False)
    annees = db.relationship(
        "ApcAnneeParcours",
        backref="parcours",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.code}>"

    def to_dict(self):
        return {
            "code": self.code,
            "numero": self.numero,
            "libelle": self.libelle,
            "annees": {x.ordre: x.to_dict() for x in self.annees},
        }


class ApcAnneeParcours(db.Model, XMLModel):
    id = db.Column(db.Integer, primary_key=True)
    parcours_id = db.Column(
        db.Integer, db.ForeignKey("apc_parcours.id"), nullable=False
    )
    ordre = db.Column(db.Integer)

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

    def to_dict(self):
        return {
            "ordre": self.ordre,
            "competences": {
                x.competence.titre: {
                    "niveau": x.niveau,
                    "id_orebut": x.competence.id_orebut,
                }
                for x in self.niveaux_competences
            },
        }


class ApcParcoursNiveauCompetence(db.Model):
    """Association entre année de parcours et compétence.
    Le "niveau" de la compétence est donné ici
    (convention Orébut)
    """

    competence_id = db.Column(
        db.Integer,
        db.ForeignKey("apc_competence.id", ondelete="CASCADE"),
        primary_key=True,
    )
    annee_parcours_id = db.Column(
        db.Integer,
        db.ForeignKey("apc_annee_parcours.id", ondelete="CASCADE"),
        primary_key=True,
    )
    niveau = db.Column(db.Integer, nullable=False)  # 1, 2, 3
    competence = db.relationship(
        ApcCompetence,
        backref=db.backref(
            "annee_parcours",
            passive_deletes=True,
            cascade="save-update, merge, delete, delete-orphan",
        ),
    )
    annee_parcours = db.relationship(
        ApcAnneeParcours,
        backref=db.backref(
            "niveaux_competences",
            passive_deletes=True,
            cascade="save-update, merge, delete, delete-orphan",
        ),
    )

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.competence} {self.annee_parcours}>"