##############################################################################
# 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 enum import unique
from typing import Any

from app import db

from app.scodoc.sco_utils import ModuleType


class XMLModel:
    _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.
        """
        return {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}

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


class ApcReferentielCompetences(db.Model, XMLModel):
    id = db.Column(db.Integer, primary_key=True)
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
    specialite = db.Column(db.Text())
    specialite_long = db.Column(db.Text())
    type_titre = db.Column(db.Text())
    _xml_attribs = {  # Orébut xml attrib : attribute
        "type": "type_titre",
    }
    # 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 to_dict(self):
        """Représentation complète du ref. de comp.
        comme un dict.
        """
        return {
            "dept_id": self.dept_id,
            "specialite": self.specialite,
            "specialite_long": self.specialite_long,
            "type_titre": self.type_titre,
            "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):
    __table_args__ = (
        # les compétences dans Orébut sont identifiées par leur "titre"
        # unique au sein d'un référentiel:
        db.UniqueConstraint(
            "referentiel_id", "titre", name="apc_competence_referentiel_id_titre_key"
        ),
    )
    id = db.Column(db.Integer, primary_key=True)
    referentiel_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
    )
    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
        "name": "titre",
        "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 to_dict(self):
        return {
            "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):
    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):
    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 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):
        return {"libelle": self.libelle}

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

    def __repr__(self):
        return "<AppCritique {}>".format(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 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 to_dict(self):
        return {
            "ordre": self.ordre,
            "competences": {
                x.competence.titre: {"niveau": x.niveau}
                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",
        ),
    )
