# -*- coding: UTF-8 -*

"""Définition d'un étudiant
    et données rattachées (adresses, annotations, ...)
"""

from functools import cached_property
from flask import abort, url_for
from flask import g, request
import sqlalchemy

from app import db
from app import models

from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat
import app.scodoc.sco_utils as scu


class Identite(db.Model):
    """étudiant"""

    __tablename__ = "identite"
    __table_args__ = (
        db.UniqueConstraint("dept_id", "code_nip"),
        db.UniqueConstraint("dept_id", "code_ine"),
    )

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

    nom = db.Column(db.Text())
    prenom = db.Column(db.Text())
    nom_usuel = db.Column(db.Text())
    # optionnel (si present, affiché à la place du nom)
    civilite = db.Column(db.String(1), nullable=False)
    __table_args__ = (db.CheckConstraint("civilite IN ('M', 'F', 'X')"),)

    date_naissance = db.Column(db.Date)
    lieu_naissance = db.Column(db.Text())
    dept_naissance = db.Column(db.Text())
    nationalite = db.Column(db.Text())
    statut = db.Column(db.Text())
    boursier = db.Column(db.Boolean())  # True si boursier ('O' en ScoDoc7)
    photo_filename = db.Column(db.Text())
    # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
    code_nip = db.Column(db.Text(), index=True)
    code_ine = db.Column(db.Text(), index=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)
    #
    adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
    billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
    # one-to-one relation:
    admission = db.relationship("Admission", backref="identite", lazy="dynamic")

    def __repr__(self):
        return f"<Etud {self.id} {self.nom} {self.prenom}>"

    @classmethod
    def from_request(cls, etudid=None, code_nip=None):
        """Etudiant à partir de l'etudid ou du code_nip, soit
        passés en argument soit retrouvés directement dans la requête web.
        Erreur 404 si inexistant.
        """
        args = make_etud_args(etudid=etudid, code_nip=code_nip)
        return Identite.query.filter_by(**args).first_or_404()

    @property
    def civilite_str(self):
        """returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
        personnes ne souhaitant pas d'affichage).
        """
        return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]

    def sex_nom(self, no_accents=False) -> str:
        "'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'"
        s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}"
        if no_accents:
            return scu.suppress_accents(s)
        return s

    def nom_disp(self) -> str:
        "Nom à afficher"
        if self.nom_usuel:
            return (
                (self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
            )
        else:
            return self.nom

    @cached_property
    def nomprenom(self, reverse=False) -> str:
        """Civilité/nom/prenom pour affichages: "M. Pierre Dupont"
        Si reverse, "Dupont Pierre", sans civilité.
        """
        nom = self.nom_usuel or self.nom
        prenom = self.prenom_str
        if reverse:
            fields = (nom, prenom)
        else:
            fields = (self.civilite_str, prenom, nom)
        return " ".join([x for x in fields if x])

    @property
    def prenom_str(self):
        """Prénom à afficher. Par exemple: "Jean-Christophe" """
        if not self.prenom:
            return ""
        frags = self.prenom.split()
        r = []
        for frag in frags:
            fields = frag.split("-")
            r.append("-".join([x.lower().capitalize() for x in fields]))
        return " ".join(r)

    @cached_property
    def sort_key(self) -> tuple:
        "clé pour tris par ordre alphabétique"
        return (self.nom_usuel or self.nom).lower(), self.prenom.lower()

    def get_first_email(self, field="email") -> str:
        "Le mail associé à la première adrese de l'étudiant, ou None"
        return self.adresses[0].email or None if self.adresses.count() > 0 else None

    def to_dict_scodoc7(self):
        """Représentation dictionnaire,
        compatible ScoDoc7 mais sans infos admission
        """
        e = dict(self.__dict__)
        e.pop("_sa_instance_state", None)
        # ScoDoc7 output_formators: (backward compat)
        e["etudid"] = self.id
        e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
        e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
        return {k: e[k] or "" for k in e}  # convert_null_outputs_to_empty

    def to_dict_bul(self, include_urls=True):
        """Infos exportées dans les bulletins"""
        from app.scodoc import sco_photos

        d = {
            "civilite": self.civilite,
            "code_ine": self.code_ine,
            "code_nip": self.code_nip,
            "date_naissance": self.date_naissance.isoformat()
            if self.date_naissance
            else None,
            "email": self.get_first_email(),
            "emailperso": self.get_first_email("emailperso"),
            "etudid": self.id,
            "nom": self.nom_disp(),
            "prenom": self.prenom,
        }
        if include_urls:
            d["fiche_url"] = url_for(
                "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=self.id
            )
            d["photo_url"] = (sco_photos.get_etud_photo_url(self.id),)
        return d

    def inscription_courante(self):
        """La première inscription à un formsemestre _actuellement_ en cours.
        None s'il n'y en a pas (ou plus, ou pas encore).
        """
        r = [
            ins
            for ins in self.formsemestre_inscriptions
            if ins.formsemestre.est_courant()
        ]
        return r[0] if r else None

    def inscription_courante_date(self, date_debut, date_fin):
        """La première inscription à un formsemestre incluant la
        période [date_debut, date_fin]
        """
        r = [
            ins
            for ins in self.formsemestre_inscriptions
            if ins.formsemestre.contient_periode(date_debut, date_fin)
        ]
        return r[0] if r else None

    def etat_inscription(self, formsemestre_id):
        """etat de l'inscription de cet étudiant au semestre:
        False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
        """
        # voir si ce n'est pas trop lent:
        ins = models.FormSemestreInscription.query.filter_by(
            etudid=self.id, formsemestre_id=formsemestre_id
        ).first()
        if ins:
            return ins.etat
        return False


def make_etud_args(
    etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
) -> dict:
    """forme args dict pour requete recherche etudiant
    On peut specifier etudid
    ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
    (dans cet ordre).

    Résultat: dict avec soit "etudid", soit "code_nip", soit "code_ine"
    """
    args = None
    if etudid:
        args = {"etudid": etudid}
    elif code_nip:
        args = {"code_nip": code_nip}
    elif use_request:  # use form from current request (Flask global)
        if request.method == "POST":
            vals = request.form
        elif request.method == "GET":
            vals = request.args
        else:
            vals = {}
        if "etudid" in vals:
            args = {"etudid": int(vals["etudid"])}
        elif "code_nip" in vals:
            args = {"code_nip": str(vals["code_nip"])}
        elif "code_ine" in vals:
            args = {"code_ine": str(vals["code_ine"])}
    if not args:
        if abort_404:
            abort(404, "pas d'étudiant sélectionné")
        elif raise_exc:
            raise ValueError("make_etud_args: pas d'étudiant sélectionné !")
    return args


class Adresse(db.Model):
    """Adresse d'un étudiant
    (le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
    """

    __tablename__ = "adresse"

    id = db.Column(db.Integer, primary_key=True)
    adresse_id = db.synonym("id")
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id"),
    )
    email = db.Column(db.Text())  # mail institutionnel
    emailperso = db.Column(db.Text)  # email personnel (exterieur)
    domicile = db.Column(db.Text)
    codepostaldomicile = db.Column(db.Text)
    villedomicile = db.Column(db.Text)
    paysdomicile = db.Column(db.Text)
    telephone = db.Column(db.Text)
    telephonemobile = db.Column(db.Text)
    fax = db.Column(db.Text)
    typeadresse = db.Column(
        db.Text, default="domicile", server_default="domicile", nullable=False
    )
    description = db.Column(db.Text)


class Admission(db.Model):
    """Informations liées à l'admission d'un étudiant"""

    __tablename__ = "admissions"

    id = db.Column(db.Integer, primary_key=True)
    adm_id = db.synonym("id")
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id"),
    )
    # Anciens champs de ScoDoc7, à revoir pour être plus générique et souple
    # notamment dans le cadre du bac 2021
    # de plus, certaines informations liées à APB ne sont plus disponibles
    # avec Parcoursup
    annee = db.Column(db.Integer)
    bac = db.Column(db.Text)
    specialite = db.Column(db.Text)
    annee_bac = db.Column(db.Integer)
    math = db.Column(db.Text)
    physique = db.Column(db.Float)
    anglais = db.Column(db.Float)
    francais = db.Column(db.Float)
    # Rang dans les voeux du candidat (inconnu avec APB et PS)
    rang = db.Column(db.Integer)
    # Qualité et décision du jury d'admission (ou de l'examinateur)
    qualite = db.Column(db.Float)
    rapporteur = db.Column(db.Text)
    decision = db.Column(db.Text)
    score = db.Column(db.Float)
    commentaire = db.Column(db.Text)
    # Lycée d'origine:
    nomlycee = db.Column(db.Text)
    villelycee = db.Column(db.Text)
    codepostallycee = db.Column(db.Text)
    codelycee = db.Column(db.Text)
    # 'APB', 'APC-PC', 'CEF', 'Direct', '?' (autre)
    type_admission = db.Column(db.Text)
    # était boursier dans le cycle precedent (lycee) ?
    boursier_prec = db.Column(db.Boolean())
    # classement par le jury d'admission (1 à N),
    # global (pas celui d'APB si il y a des groupes)
    classement = db.Column(db.Integer)
    # code du groupe APB
    apb_groupe = db.Column(db.Text)
    # classement (1..Ngr) par le jury dans le groupe APB
    apb_classement_gr = db.Column(db.Integer)

    def get_bac(self) -> Baccalaureat:
        "Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
        return Baccalaureat(self.bac, specialite=self.specialite)

    def to_dict(self, no_nulls=False):
        """Représentation dictionnaire,"""
        e = dict(self.__dict__)
        e.pop("_sa_instance_state", None)
        if no_nulls:
            for k in e:
                if e[k] is None:
                    col_type = getattr(
                        sqlalchemy.inspect(models.Admission).columns, "apb_groupe"
                    ).expression.type
                    if isinstance(col_type, sqlalchemy.Text):
                        e[k] = ""
                    elif isinstance(col_type, sqlalchemy.Integer):
                        e[k] = 0
                    elif isinstance(col_type, sqlalchemy.Boolean):
                        e[k] = False
        return e


# Suivi scolarité / débouchés
class ItemSuivi(db.Model):
    __tablename__ = "itemsuivi"

    id = db.Column(db.Integer, primary_key=True)
    itemsuivi_id = db.synonym("id")
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id"),
    )
    item_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
    situation = db.Column(db.Text)


class ItemSuiviTag(db.Model):
    __tablename__ = "itemsuivi_tags"
    id = db.Column(db.Integer, primary_key=True)
    dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
    tag_id = db.synonym("id")
    title = db.Column(db.Text(), nullable=False, unique=True)


# Association tag <-> module
itemsuivi_tags_assoc = db.Table(
    "itemsuivi_tags_assoc",
    db.Column(
        "tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id", ondelete="CASCADE")
    ),
    db.Column(
        "itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id", ondelete="CASCADE")
    ),
)


class EtudAnnotation(db.Model):
    """Annotation sur un étudiant"""

    __tablename__ = "etud_annotations"

    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
    etudid = db.Column(db.Integer)  # sans contrainte (compat ScoDoc 7))
    author = db.Column(db.Text)  # le pseudo (user_name), was zope_authenticated_user
    comment = db.Column(db.Text)