# -*- coding: UTF-8 -*

"""Décisions de jury (validations) des RCUE et années du BUT
"""
from collections import defaultdict

import sqlalchemy as sa

from app import db
from app.models import CODE_STR_LEN, ScoDocModel
from app.models.but_refcomp import ApcNiveau
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
from app.scodoc import sco_preferences
from app.scodoc import sco_utils as scu


class ApcValidationRCUE(ScoDocModel):
    """Validation des niveaux de compétences

    aka "regroupements cohérents d'UE" dans le jargon BUT.

    Le formsemestre est l'origine, utilisé pour effacer
    """

    __tablename__ = "apc_validation_rcue"
    # Assure unicité de la décision:
    __table_args__ = (
        db.UniqueConstraint("etudid", "formsemestre_id", "ue1_id", "ue2_id"),
    )

    id = db.Column(db.Integer, primary_key=True)
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    formsemestre_id = db.Column(
        db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
    )
    "formsemestre origine du RCUE (celui d'où a été émis la validation)"
    # Les deux UE associées à ce niveau:
    ue1_id = db.Column(
        db.Integer, db.ForeignKey("notes_ue.id", ondelete="CASCADE"), nullable=False
    )
    ue2_id = db.Column(
        db.Integer, db.ForeignKey("notes_ue.id", ondelete="CASCADE"), nullable=False
    )
    # optionnel, le parcours dans lequel se trouve la compétence:
    parcours_id = db.Column(
        db.Integer, db.ForeignKey("apc_parcours.id", ondelete="set null"), nullable=True
    )
    date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
    code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)

    etud = db.relationship("Identite", backref="apc_validations_rcues")
    formsemestre = db.relationship("FormSemestre", backref="apc_validations_rcues")
    ue1 = db.relationship("UniteEns", foreign_keys=ue1_id)
    ue2 = db.relationship("UniteEns", foreign_keys=ue2_id)
    parcour = db.relationship("ApcParcours")

    _sco_dept_relations = ("Identite",)  # pour accéder au département

    def __repr__(self):
        return f"""<{self.__class__.__name__} {self.id} {self.etud} {
            self.ue1}/{self.ue2}:{self.code!r}>"""

    def __str__(self):
        return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {
            self.code} enregistrée le {self.date.strftime(scu.DATE_FMT)}"""

    def html(self) -> str:
        "description en HTML"
        return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
                <b>{self.code}</b>
                <em>enregistrée le {self.date.strftime(scu.DATEATIME_FMT)}</em>"""

    def annee(self) -> str:
        """l'année BUT concernée: "BUT1", "BUT2" ou "BUT3" """
        niveau = self.niveau()
        return niveau.annee if niveau else None

    def niveau(self) -> ApcNiveau | None:
        """Le niveau de compétence associé à cet RCUE."""
        # Par convention, il est donné par la seconde UE
        # à défaut (si l'UE a été désacciée entre temps), la première
        # et à défaut, renvoie None
        return self.ue2.niveau_competence or self.ue1.niveau_competence

    def to_dict(self):
        "as a dict"
        d = dict(self.__dict__)
        d.pop("_sa_instance_state", None)
        d["etud"] = self.etud.to_dict_short()
        d["ue1"] = self.ue1.to_dict()
        d["ue2"] = self.ue2.to_dict()

        return d

    def to_dict_bul(self) -> dict:
        "Export dict pour bulletins: le code et le niveau de compétence"
        niveau = self.niveau()
        return {
            "code": self.code,
            "niveau": None if niveau is None else niveau.to_dict_bul(),
        }

    def to_dict_codes(self) -> dict:
        "Dict avec seulement les ids et la date - pour cache table jury"
        return {
            "id": self.id,
            "code": self.code,
            "date": self.date,
            "etudid": self.etudid,
            "niveau_id": self.niveau().id,
            "formsemestre_id": self.formsemestre_id,
        }

    def get_codes_apogee(self) -> set[str]:
        """Les codes Apogée associés à cette validation RCUE.
        Prend les codes des deux UEs
        """
        return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue()


class ApcValidationAnnee(ScoDocModel):
    """Validation des années du BUT"""

    __tablename__ = "apc_validation_annee"
    # Assure unicité de la décision:
    __table_args__ = (
        db.UniqueConstraint("etudid", "ordre", "referentiel_competence_id"),
    )
    id = db.Column(db.Integer, primary_key=True)
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    ordre = db.Column(db.Integer, nullable=False)
    "numéro de l'année: 1, 2, 3"
    formsemestre_id = db.Column(
        db.Integer, db.ForeignKey("notes_formsemestre.id"), nullable=True
    )
    "le semestre origine, normalement l'IMPAIR (le 1er) de l'année"
    referentiel_competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
    )
    annee_scolaire = db.Column(db.Integer, nullable=False)  # eg 2021
    date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
    code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)

    etud = db.relationship("Identite", backref="apc_validations_annees")
    formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees")

    _sco_dept_relations = ("Identite",)  # pour accéder au département

    def __repr__(self):
        return f"""<{self.__class__.__name__} {self.id} {self.etud
            } BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"""

    def __str__(self):
        return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}"""

    def to_dict_bul(self) -> dict:
        "dict pour bulletins"
        return {
            "annee_scolaire": self.annee_scolaire,
            "date": self.date.isoformat() if self.date else "",
            "code": self.code,
            "ordre": self.ordre,
        }

    def html(self) -> str:
        "Affichage html"
        date_str = (
            f"""le {self.date.strftime(scu.DATEATIME_FMT)}"""
            if self.date
            else "(sans date)"
        )
        link = (
            self.formsemestre.html_link_status(
                label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}",
                title=self.formsemestre.titre_annee(),
            )
            if self.formsemestre
            else "externe/antérieure"
        )
        return f"""Validation <b>année BUT{self.ordre}</b> émise par
            {link}
            : <b>{self.code}</b>
            {date_str}
        """


def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
    """
    Un dict avec les décisions de jury BUT enregistrées:
     - decision_rcue  : list[dict]
     - decision_annee : dict (décision issue de ce semestre seulement (à confirmer ?))
    Ne reprend pas les décisions d'UE, non spécifiques au BUT.
    """
    decisions = {}
    # --- RCUEs: seulement sur semestres pairs XXX à améliorer
    if formsemestre.semestre_id % 2 == 0:
        # validations émises depuis ce formsemestre:
        validations_rcues = (
            ApcValidationRCUE.query.filter_by(
                etudid=etud.id, formsemestre_id=formsemestre.id
            )
            .join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
            .order_by(UniteEns.numero, UniteEns.acronyme)
        )
        decisions["decision_rcue"] = [v.to_dict_bul() for v in validations_rcues]
        titres_rcues = _build_decisions_rcue_list(decisions["decision_rcue"])
        decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
        decisions["descr_decisions_rcue_list"] = titres_rcues
        decisions["descr_decisions_niveaux"] = (
            "Niveaux de compétences: " + decisions["descr_decisions_rcue"]
        )
    else:
        decisions["decision_rcue"] = []
        decisions["descr_decisions_rcue"] = ""
        decisions["descr_decisions_niveaux"] = ""
    # --- Année: prend la validation pour l'année scolaire et l'ordre de ce semestre
    if sco_preferences.get_preference("bul_but_code_annuel", formsemestre.id):
        annee_but = (formsemestre.semestre_id + 1) // 2
        validation = ApcValidationAnnee.query.filter_by(
            etudid=etud.id,
            annee_scolaire=formsemestre.annee_scolaire(),
            ordre=annee_but,
            referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
        ).first()
        if validation:
            decisions["decision_annee"] = validation.to_dict_bul()
        else:
            decisions["decision_annee"] = None
    else:
        decisions["decision_annee"] = None
    return decisions


def _build_decisions_rcue_list(decisions_rcue: dict) -> list[str]:
    """Formatte liste des décisions niveaux de compétences / RCUE pour
    lettres individuelles.
    Le résulat est trié par compétence et donne pour chaque niveau avec validation:
    [ 'Administrer: niveau 1 ADM, niveau 2 ADJ', ... ]
    """
    # Construit { id_competence : validation }
    # où validation est {'code': 'CMP', 'niveau': {'annee': 'BUT3', 'competence': {}, ... }
    validation_by_competence = defaultdict(list)
    for validation in decisions_rcue:
        if validation:
            # Attention, certaines validations de RCUE peuvent ne plus être associées
            # à un niveau de compétence si l'UE a été déassociée (ce qui ne devrait pas être fait)
            competence_id = (
                (validation.get("niveau") or {}).get("competence") or {}
            ).get("id_orebut")
            validation_by_competence[competence_id].append(validation)
    # Tri des listes de validation par numéro de compétence
    validations_niveaux = sorted(
        validation_by_competence.values(),
        key=lambda v: (
            ((v[0].get("niveau") or {}).get("competence") or {}).get("numero", 0)
            if v
            else -1
        ),
    )
    titres_rcues = []
    empty = {}  # pour syntaxe f-string
    for validations in validations_niveaux:
        if validations:
            v = validations[0]
            titre_competence = ((v.get("niveau") or {}).get("competence", {})).get(
                "titre", "sans titre ! A vérifier !"
            )
            titres_rcues.append(
                f"""{titre_competence} : """
                + ", ".join(
                    [
                        f"niveau {((v.get('niveau') or empty).get('ordre') or '?')} {v.get('code', '?')}"
                        for v in validations
                    ]
                )
            )
    return titres_rcues


class ValidationDUT120(ScoDocModel):
    """Validations du DUT 120
    Ce diplôme est attribué sur demande aux étudiants de BUT ayant acquis les 120 ECTS
    de BUT 1 et BUT 2.
    """

    id = db.Column(db.Integer, primary_key=True)
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id", ondelete="CASCADE"),
        nullable=False,
    )
    """le semestre origine, dans la plupart des cas le S4 (le diplôme DUT120
    apparaîtra sur les PV de ce formsemestre)"""
    referentiel_competence_id = db.Column(
        db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
    )  # pas de cascade, on ne doit pas supprimer un référentiel utilisé
    """Identifie la spécialité de DUT décernée"""
    date = db.Column(
        db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
    )
    """Date de délivrance"""

    etud = db.relationship("Identite", backref="validations_dut120")
    formsemestre = db.relationship("FormSemestre", backref="validations_dut120")

    _sco_dept_relations = ("Identite",)  # pour accéder au département

    def __repr__(self):
        return f"""<ValidationDUT120 {self.etud}>"""

    def html(self) -> str:
        "Affichage html"
        date_str = (
            f"""le {self.date.strftime(scu.DATEATIME_FMT)}"""
            if self.date
            else "(sans date)"
        )
        link = (
            self.formsemestre.html_link_status(
                label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}",
                title=self.formsemestre.titre_annee(),
            )
            if self.formsemestre
            else "externe/antérieure"
        )
        specialite = (
            self.formsemestre.formation.referentiel_competence.get_title()
            if self.formsemestre.formation.referentiel_competence
            else "(désassociée!)"
        )
        return f"""Diplôme de <b>DUT en 120 ECTS du {specialite}</b> émis par
            {link}
            {date_str}
        """