# -*- coding: UTF-8 -*
"""Gestion de l'assiduité (assiduités + justificatifs)
"""
from datetime import datetime

from app import db
from app.models import ModuleImpl
from app.models.etudiants import Identite
from app.scodoc.sco_utils import (
    EtatAssiduite,
    EtatJustificatif,
    localize_datetime,
    is_period_overlapping,
)
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_utils import (
    EtatAssiduite,
    EtatJustificatif,
    localize_datetime,
)


class Assiduite(db.Model):
    """
    Représente une assiduité:
        - une plage horaire lié à un état et un étudiant
        - un module si spécifiée
        - une description si spécifiée
    """

    __tablename__ = "assiduites"

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    assiduite_id = db.synonym("id")

    date_debut = db.Column(
        db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
    )
    date_fin = db.Column(
        db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
    )

    moduleimpl_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_moduleimpl.id", ondelete="SET NULL"),
    )
    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    etat = db.Column(db.Integer, nullable=False)

    desc = db.Column(db.Text)

    entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())

    user_id = db.Column(
        db.Integer,
        db.ForeignKey("user.id", ondelete="SET NULL"),
        nullable=True,
    )

    est_just = db.Column(db.Boolean, server_default="false", nullable=False)

    def to_dict(self, format_api=True) -> dict:
        """Retourne la représentation json de l'assiduité"""
        etat = self.etat

        if format_api:
            etat = EtatAssiduite.inverse().get(self.etat).name
        data = {
            "assiduite_id": self.id,
            "etudid": self.etudid,
            "moduleimpl_id": self.moduleimpl_id,
            "date_debut": self.date_debut,
            "date_fin": self.date_fin,
            "etat": etat,
            "desc": self.desc,
            "entry_date": self.entry_date,
            "user_id": self.user_id,
            "est_just": self.est_just,
        }
        return data

    @classmethod
    def create_assiduite(
        cls,
        etud: Identite,
        date_debut: datetime,
        date_fin: datetime,
        etat: EtatAssiduite,
        moduleimpl: ModuleImpl = None,
        description: str = None,
        entry_date: datetime = None,
        user_id: int = None,
        est_just: bool = False,
    ) -> object or int:
        """Créer une nouvelle assiduité pour l'étudiant"""
        # Vérification de non duplication des périodes
        assiduites: list[Assiduite] = etud.assiduites
        if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
            raise ScoValueError(
                "Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
            )
        if moduleimpl is not None:
            # Vérification de l'existence du module pour l'étudiant
            if moduleimpl.est_inscrit(etud):
                nouv_assiduite = Assiduite(
                    date_debut=date_debut,
                    date_fin=date_fin,
                    etat=etat,
                    etudiant=etud,
                    moduleimpl_id=moduleimpl.id,
                    desc=description,
                    entry_date=entry_date,
                    user_id=user_id,
                    est_just=est_just,
                )
            else:
                raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
        else:
            nouv_assiduite = Assiduite(
                date_debut=date_debut,
                date_fin=date_fin,
                etat=etat,
                etudiant=etud,
                desc=description,
                entry_date=entry_date,
                user_id=user_id,
                est_just=est_just,
            )

        return nouv_assiduite

    @classmethod
    def fast_create_assiduite(
        cls,
        etudid: int,
        date_debut: datetime,
        date_fin: datetime,
        etat: EtatAssiduite,
        moduleimpl_id: int = None,
        description: str = None,
        entry_date: datetime = None,
        est_just: bool = False,
    ) -> object or int:
        """Créer une nouvelle assiduité pour l'étudiant"""
        # Vérification de non duplication des périodes

        nouv_assiduite = Assiduite(
            date_debut=date_debut,
            date_fin=date_fin,
            etat=etat,
            etudid=etudid,
            moduleimpl_id=moduleimpl_id,
            desc=description,
            entry_date=entry_date,
            est_just=est_just,
        )

        return nouv_assiduite


class Justificatif(db.Model):
    """
    Représente un justificatif:
        - une plage horaire lié à un état et un étudiant
        - une raison si spécifiée
        - un fichier si spécifié
    """

    __tablename__ = "justificatifs"

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

    date_debut = db.Column(
        db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
    )
    date_fin = db.Column(
        db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
    )

    etudid = db.Column(
        db.Integer,
        db.ForeignKey("identite.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    etat = db.Column(
        db.Integer,
        nullable=False,
    )

    entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())

    user_id = db.Column(
        db.Integer,
        db.ForeignKey("user.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )

    raison = db.Column(db.Text())

    # Archive_id -> sco_archives_justificatifs.py
    fichier = db.Column(db.Text())

    def to_dict(self, format_api: bool = False) -> dict:
        """transformation de l'objet en dictionnaire sérialisable"""

        etat = self.etat

        if format_api:
            etat = EtatJustificatif.inverse().get(self.etat).name

        data = {
            "justif_id": self.justif_id,
            "etudid": self.etudid,
            "date_debut": self.date_debut,
            "date_fin": self.date_fin,
            "etat": etat,
            "raison": self.raison,
            "fichier": self.fichier,
            "entry_date": self.entry_date,
            "user_id": self.user_id,
        }
        return data

    @classmethod
    def create_justificatif(
        cls,
        etud: Identite,
        date_debut: datetime,
        date_fin: datetime,
        etat: EtatJustificatif,
        raison: str = None,
        entry_date: datetime = None,
        user_id: int = None,
    ) -> object or int:
        """Créer un nouveau justificatif pour l'étudiant"""
        nouv_justificatif = Justificatif(
            date_debut=date_debut,
            date_fin=date_fin,
            etat=etat,
            etudiant=etud,
            raison=raison,
            entry_date=entry_date,
            user_id=user_id,
        )
        return nouv_justificatif

    @classmethod
    def fast_create_justificatif(
        cls,
        etudid: int,
        date_debut: datetime,
        date_fin: datetime,
        etat: EtatJustificatif,
        raison: str = None,
        entry_date: datetime = None,
    ) -> object or int:
        """Créer un nouveau justificatif pour l'étudiant"""

        nouv_justificatif = Justificatif(
            date_debut=date_debut,
            date_fin=date_fin,
            etat=etat,
            etudid=etudid,
            raison=raison,
            entry_date=entry_date,
        )

        return nouv_justificatif


def is_period_conflicting(
    date_debut: datetime,
    date_fin: datetime,
    collection: list[Assiduite or Justificatif],
    collection_cls: Assiduite or Justificatif,
) -> bool:
    """
    Vérifie si une date n'entre pas en collision
    avec les justificatifs ou assiduites déjà présentes
    """

    date_debut = localize_datetime(date_debut)
    date_fin = localize_datetime(date_fin)

    if (
        collection.filter_by(date_debut=date_debut, date_fin=date_fin).first()
        is not None
    ):
        return True

    count: int = collection.filter(
        collection_cls.date_debut < date_fin, collection_cls.date_fin > date_debut
    ).count()

    return count > 0


def compute_assiduites_justified(
    justificatifs: Justificatif = Justificatif, reset: bool = False
) -> list[int]:
    """Calcule et modifie les champs "est_just" de chaque assiduité lié à l'étud
    retourne la liste des assiduite_id justifiées

    Si reset alors : met à false toutes les assiduités non justifiées par les justificatifs donnés
    """

    list_assiduites_id: set[int] = set()
    for justi in justificatifs:
        assiduites: Assiduite = (
            Assiduite.query.join(Justificatif, Justificatif.etudid == Assiduite.etudid)
            .filter(justi.etat == EtatJustificatif.VALIDE)
            .filter(
                Assiduite.date_debut < justi.date_fin,
                Assiduite.date_fin > justi.date_debut,
            )
        )

        for assi in assiduites:
            assi.est_just = True
            list_assiduites_id.add(assi.id)
            db.session.add(assi)

    if reset:
        un_justified: Assiduite = Assiduite.query.filter(
            Assiduite.id.not_in(list_assiduites_id)
        ).join(Justificatif, Justificatif.etudid == Assiduite.etudid)

        for assi in un_justified:
            assi.est_just = False
            db.session.add(assi)

    db.session.commit()
    return list(list_assiduites_id)
