# -*- coding: UTF-8 -*

"""ScoDoc models: formsemestre
"""
import datetime
from typing import Any

import flask_sqlalchemy

from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models import UniteEns

import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc import sco_evaluation_db
from app.models.ues import UniteEns
from app.models.modules import Module
from app.models.moduleimpls import ModuleImpl
from app.models.etudiants import Identite
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_preferences
from app.scodoc.sco_vdi import ApoEtapeVDI


class FormSemestre(db.Model):
    """Mise en oeuvre d'un semestre de formation"""

    __tablename__ = "notes_formsemestre"

    id = db.Column(db.Integer, primary_key=True)
    formsemestre_id = db.synonym("id")
    # dept_id est aussi dans la formation, ajouté ici pour
    # simplifier et accélérer les selects dans notesdb
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
    formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
    semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
    titre = db.Column(db.Text())
    date_debut = db.Column(db.Date())
    date_fin = db.Column(db.Date())
    etat = db.Column(
        db.Boolean(), nullable=False, default=True, server_default="true"
    )  # False si verrouillé
    modalite = db.Column(
        db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
    )  # "FI", "FAP", "FC", ...
    # gestion compensation sem DUT:
    gestion_compensation = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # ne publie pas le bulletin XML:
    bul_hide_xml = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # Bloque le calcul des moyennes (générale et d'UE)
    block_moyennes = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # semestres decales (pour gestion jurys):
    gestion_semestrielle = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # couleur fond bulletins HTML:
    bul_bgcolor = db.Column(
        db.String(SHORT_STR_LEN), default="white", server_default="white"
    )
    # autorise resp. a modifier semestre:
    resp_can_edit = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # autorise resp. a modifier slt les enseignants:
    resp_can_change_ens = db.Column(
        db.Boolean(), nullable=False, default=True, server_default="true"
    )
    # autorise les ens a creer des evals:
    ens_can_edit_eval = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="False"
    )
    # code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'
    elt_sem_apo = db.Column(db.Text())  # peut être fort long !
    # code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
    elt_annee_apo = db.Column(db.Text())

    # Relations:
    etapes = db.relationship(
        "FormsemestreEtape", cascade="all,delete", backref="formsemestre"
    )
    modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
    etuds = db.relationship(
        "Identite",
        secondary="notes_formsemestre_inscription",
        viewonly=True,
        lazy="dynamic",
    )
    responsables = db.relationship(
        "User",
        secondary="notes_formsemestre_responsables",
        lazy=True,
        backref=db.backref("formsemestres", lazy=True),
    )
    # Ancien id ScoDoc7 pour les migrations de bases anciennes
    # ne pas utiliser après migrate_scodoc7_dept_archives
    scodoc7_id = db.Column(db.Text(), nullable=True)

    def __init__(self, **kwargs):
        super(FormSemestre, self).__init__(**kwargs)
        if self.modalite is None:
            self.modalite = FormationModalite.DEFAULT_MODALITE

    def to_dict(self):
        d = dict(self.__dict__)
        d.pop("_sa_instance_state", None)
        # ScoDoc7 output_formators: (backward compat)
        d["formsemestre_id"] = self.id
        d["date_debut"] = (
            self.date_debut.strftime("%d/%m/%Y") if self.date_debut else ""
        )
        d["date_fin"] = self.date_fin.strftime("%d/%m/%Y") if self.date_fin else ""
        d["responsables"] = [u.id for u in self.responsables]
        return d

    def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
        """UE des modules de ce semestre.
        - Formations classiques: les UEs auxquelles appartiennent
          les modules mis en place dans ce semestre.
        - Formations APC / BUT: les UEs de la formation qui ont
        le même numéro de semestre que ce formsemestre.
        """
        if self.formation.get_parcours().APC_SAE:
            sem_ues = UniteEns.query.filter_by(
                formation=self.formation, semestre_idx=self.semestre_id
            )
        else:
            sem_ues = db.session.query(UniteEns).filter(
                ModuleImpl.formsemestre_id == self.id,
                Module.id == ModuleImpl.module_id,
                UniteEns.id == Module.ue_id,
            )
        if not with_sport:
            sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
        return sem_ues

    def est_courant(self) -> bool:
        """Vrai si la date actuelle (now) est dans le semestre
        (les dates de début et fin sont incluses)
        """
        today = datetime.date.today()
        return (self.date_debut <= today) and (today <= self.date_fin)

    def est_decale(self):
        """Vrai si semestre "décalé"
        c'est à dire semestres impairs commençant entre janvier et juin
        et les pairs entre juillet et decembre
        """
        if self.semestre_id <= 0:
            return False  # formations sans semestres
        return (self.semestre_id % 2 and self.date_debut.month <= 6) or (
            not self.semestre_id % 2 and self.date_debut.month > 6
        )

    def etapes_apo_str(self) -> str:
        """Chaine décrivant les étapes de ce semestre
        ex: "V1RT, V1RT3, V1RT4"
        """
        if not self.etapes:
            return ""
        return ", ".join([str(x.etape_apo) for x in self.etapes])

    def responsables_str(self, abbrev_prenom=True) -> str:
        """chaîne "J. Dupond, X. Martin"
        ou  "Jacques Dupond, Xavier Martin"
        """
        if not self.responsables:
            return ""
        if abbrev_prenom:
            return ", ".join([u.get_prenomnom() for u in self.responsables])
        else:
            return ", ".join([u.get_nomcomplet() for u in self.responsables])

    def annee_scolaire_str(self):
        "2021 - 2022"
        return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)

    def session_id(self) -> str:
        """identifiant externe de semestre de formation
        Exemple:  RT-DUT-FI-S1-ANNEE

        DEPT-TYPE-MODALITE+-S?|SPECIALITE

        TYPE=DUT|LP*|M*
        MODALITE=FC|FI|FA (si plusieurs, en inverse alpha)

        SPECIALITE=[A-Z]+   EON,ASSUR, ... (si pas Sn ou SnD)

        ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013)
        """
        imputation_dept = sco_preferences.get_preference("ImputationDept", self.id)
        if not imputation_dept:
            imputation_dept = sco_preferences.get_preference("DeptName")
        imputation_dept = imputation_dept.upper()
        parcours_name = self.formation.get_parcours().NAME
        modalite = self.modalite
        # exception pour code Apprentissage:
        modalite = (modalite or "").replace("FAP", "FA").replace("APP", "FA")
        if self.semestre_id > 0:
            decale = "D" if self.est_decale() else ""
            semestre_id = f"S{self.semestre_id}{decale}"
        else:
            semestre_id = self.formation.code_specialite or ""
        annee_sco = str(
            scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
        )
        return scu.sanitize_string(
            "-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco))
        )


# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table(
    "notes_formsemestre_responsables",
    db.Column(
        "formsemestre_id",
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    ),
    db.Column("responsable_id", db.Integer, db.ForeignKey("user.id")),
)


class FormsemestreEtape(db.Model):
    """Étape Apogée associées au semestre"""

    __tablename__ = "notes_formsemestre_etapes"
    id = db.Column(db.Integer, primary_key=True)
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    )
    etape_apo = db.Column(db.String(APO_CODE_STR_LEN))

    def __repr__(self):
        return f"<Etape {self.id} apo={self.etape_apo}>"

    def as_apovdi(self):
        return ApoEtapeVDI(self.etape_apo)


class FormationModalite(db.Model):
    """Modalités de formation, utilisées pour la présentation
    (grouper les semestres, générer des codes, etc.)
    """

    __tablename__ = "notes_form_modalites"

    DEFAULT_MODALITE = "FI"

    id = db.Column(db.Integer, primary_key=True)
    modalite = db.Column(
        db.String(SHORT_STR_LEN),
        unique=True,
        index=True,
        default=DEFAULT_MODALITE,
        server_default=DEFAULT_MODALITE,
    )  # code
    titre = db.Column(db.Text())  # texte explicatif
    # numero = ordre de presentation)
    numero = db.Column(db.Integer)

    @staticmethod
    def insert_modalites():
        """Create default modalities"""
        numero = 0
        try:
            for (code, titre) in (
                (FormationModalite.DEFAULT_MODALITE, "Formation Initiale"),
                ("FAP", "Apprentissage"),
                ("FC", "Formation Continue"),
                ("DEC", "Formation Décalées"),
                ("LIC", "Licence"),
                ("CPRO", "Contrats de Professionnalisation"),
                ("DIST", "À distance"),
                ("ETR", "À l'étranger"),
                ("EXT", "Extérieur"),
                ("OTHER", "Autres formations"),
            ):
                modalite = FormationModalite.query.filter_by(modalite=code).first()
                if modalite is None:
                    modalite = FormationModalite(
                        modalite=code, titre=titre, numero=numero
                    )
                    db.session.add(modalite)
                    numero += 1
            db.session.commit()
        except:
            db.session.rollback()
            raise


class FormsemestreUECoef(db.Model):
    """Coef des UE capitalisees arrivant dans ce semestre"""

    __tablename__ = "notes_formsemestre_uecoef"
    __table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)

    id = db.Column(db.Integer, primary_key=True)
    formsemestre_uecoef_id = db.synonym("id")
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    )
    ue_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_ue.id"),
    )
    coefficient = db.Column(db.Float, nullable=False)


class FormsemestreUEComputationExpr(db.Model):
    """Formules utilisateurs pour calcul moyenne UE"""

    __tablename__ = "notes_formsemestre_ue_computation_expr"
    __table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)

    id = db.Column(db.Integer, primary_key=True)
    notes_formsemestre_ue_computation_expr_id = db.synonym("id")
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    )
    ue_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_ue.id"),
    )
    # formule de calcul moyenne
    computation_expr = db.Column(db.Text())


class FormsemestreCustomMenu(db.Model):
    """Menu custom associe au semestre"""

    __tablename__ = "notes_formsemestre_custommenu"

    id = db.Column(db.Integer, primary_key=True)
    custommenu_id = db.synonym("id")
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    )
    title = db.Column(db.Text())
    url = db.Column(db.Text())
    idx = db.Column(db.Integer, default=0, server_default="0")  #  rang dans le menu


class FormsemestreInscription(db.Model):
    """Inscription à un semestre de formation"""

    __tablename__ = "notes_formsemestre_inscription"
    __table_args__ = (db.UniqueConstraint("formsemestre_id", "etudid"),)

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

    etudid = db.Column(db.Integer, db.ForeignKey("identite.id"))
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
    )
    etud = db.relationship(
        Identite,
        backref=db.backref("formsemestre_inscriptions", cascade="all, delete-orphan"),
    )
    formsemestre = db.relationship(
        FormSemestre,
        backref=db.backref("inscriptions", cascade="all, delete-orphan"),
    )
    # I inscrit, D demission en cours de semestre, DEF si "defaillant"
    etat = db.Column(db.String(CODE_STR_LEN))
    # etape apogee d'inscription (experimental 2020)
    etape = db.Column(db.String(APO_CODE_STR_LEN))


class NotesSemSet(db.Model):
    """semsets: ensemble de formsemestres pour exports Apogée"""

    __tablename__ = "notes_semset"

    id = db.Column(db.Integer, primary_key=True)
    semset_id = db.synonym("id")
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"))

    title = db.Column(db.Text)
    annee_scolaire = db.Column(db.Integer, nullable=True, default=None)
    # periode: 0 (année), 1 (Simpair), 2 (Spair)
    sem_id = db.Column(db.Integer, nullable=True, default=None)


# Association: many to many
notes_semset_formsemestre = db.Table(
    "notes_semset_formsemestre",
    db.Column("formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id")),
    db.Column(
        "semset_id",
        db.Integer,
        db.ForeignKey("notes_semset.id", ondelete="CASCADE"),
        nullable=False,
    ),
    db.UniqueConstraint("formsemestre_id", "semset_id"),
)