##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 API : Justificatifs
"""
from datetime import datetime

from flask_json import as_json
from flask import g, request
from flask_login import login_required, current_user
from flask_sqlalchemy.query import Query
from werkzeug.exceptions import NotFound

import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu
from app import db
from app.api import api_bp as bp
from app.api import api_web_bp
from app.api import get_model_api_object, tools
from app.decorators import permission_required, scodoc
from app.models import (
    Identite,
    Justificatif,
    Departement,
    FormSemestre,
)
from app.models.assiduites import (
    compute_assiduites_justified,
    get_formsemestre_from_data,
)
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import json_error
from app.scodoc.sco_groups import get_group_members


# Partie Modèle
@bp.route("/justificatif/<int:justif_id>")
@api_web_bp.route("/justificatif/<int:justif_id>")
@scodoc
@permission_required(Permission.ScoView)
def justificatif(justif_id: int = None):
    """Retourne un objet justificatif à partir de son id

    Exemple de résultat:
    {
        "justif_id": 1,
        "etudid": 2,
        "date_debut": "2022-10-31T08:00+01:00",
        "date_fin": "2022-10-31T10:00+01:00",
        "etat": "valide",
        "fichier": "archive_id",
        "raison": "une raison",
        "entry_date": "2022-10-31T08:00+01:00",
        "user_id": 1 or null,
    }

    """

    return get_model_api_object(Justificatif, justif_id, Identite)


# etudid
@bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
@api_web_bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
@bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
@api_web_bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
@bp.route("/justificatifs/etudid/<int:etudid>", defaults={"with_query": False})
@api_web_bp.route("/justificatifs/etudid/<int:etudid>", defaults={"with_query": False})
@bp.route("/justificatifs/etudid/<int:etudid>/query", defaults={"with_query": True})
@api_web_bp.route(
    "/justificatifs/etudid/<int:etudid>/query", defaults={"with_query": True}
)
# nip
@bp.route("/justificatifs/nip/<nip>", defaults={"with_query": False})
@api_web_bp.route("/justificatifs/nip/<nip>", defaults={"with_query": False})
@bp.route("/justificatifs/nip/<nip>/query", defaults={"with_query": True})
@api_web_bp.route("/justificatifs/nip/<nip>/query", defaults={"with_query": True})
# ine
@bp.route("/justificatifs/ine/<ine>", defaults={"with_query": False})
@api_web_bp.route("/justificatifs/ine/<ine>", defaults={"with_query": False})
@bp.route("/justificatifs/ine/<ine>/query", defaults={"with_query": True})
@api_web_bp.route("/justificatifs/ine/<ine>/query", defaults={"with_query": True})
#
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = False):
    """
    Retourne toutes les assiduités d'un étudiant
    chemin : /justificatifs/<int:etudid>

    Un filtrage peut être donné avec une query
    chemin : /justificatifs/<int:etudid>/query?

    Les différents filtres :
        Etat (etat du justificatif -> validé, non validé, modifé, en attente):
            query?etat=[- liste des états séparé par une virgule -]
            ex: .../query?etat=validé,modifié
        Date debut
        (date de début du justificatif, sont affichés les justificatifs
        dont la date de début est supérieur ou égale à la valeur donnée):
            query?date_debut=[- date au format iso -]
            ex: query?date_debut=2022-11-03T08:00+01:00
        Date fin
        (date de fin du justificatif, sont affichés les justificatifs
        dont la date de fin est inférieure ou égale à la valeur donnée):
            query?date_fin=[- date au format iso -]
            ex: query?date_fin=2022-11-03T10:00+01:00
        user_id (l'id de l'auteur du justificatif)
            query?user_id=[int]
            ex query?user_id=3
    """
    # Récupération de l'étudiant
    etud: Identite = tools.get_etud(etudid, nip, ine)

    if etud is None:
        return json_error(
            404,
            message="étudiant inconnu",
        )

    # Récupération des justificatifs de l'étudiant
    justificatifs_query = etud.justificatifs

    # Filtrage des justificatifs en fonction de la requête
    if with_query:
        justificatifs_query = _filter_manager(request, justificatifs_query)

    # Mise en forme des données puis retour en JSON
    data_set: list[dict] = []
    for just in justificatifs_query.all():
        data = just.to_dict(format_api=True)
        data_set.append(data)

    return data_set


@api_web_bp.route("/justificatifs/dept/<int:dept_id>", defaults={"with_query": False})
@api_web_bp.route(
    "/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True}
)
@bp.route("/justificatifs/dept/<int:dept_id>", defaults={"with_query": False})
@bp.route("/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True})
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
    """XXX TODO missing doc"""

    # Récupération du département et des étudiants du département
    dept: Departement = Departement.query.get_or_404(dept_id)
    etuds: list[int] = [etud.id for etud in dept.etudiants]

    # Récupération des justificatifs des étudiants du département
    justificatifs_query: Query = Justificatif.query.filter(
        Justificatif.etudid.in_(etuds)
    )

    # Filtrage des justificatifs
    if with_query:
        justificatifs_query: Query = _filter_manager(request, justificatifs_query)

    # Mise en forme des données et retour JSON
    data_set: list[dict] = []
    for just in justificatifs_query:
        data_set.append(_set_sems(just))

    return data_set


def _set_sems(justi: Justificatif) -> dict:
    """
    _set_sems Ajoute le formsemestre associé au justificatif s'il existe

    Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif

    Args:
        justi (Justificatif): Le justificatif

    Returns:
        dict: La représentation de l'assiduité en dictionnaire
    """
    # Conversion du justificatif en dictionnaire
    data = justi.to_dict(format_api=True)

    # Récupération du formsemestre de l'assiduité
    formsemestre: FormSemestre = get_formsemestre_from_data(justi.to_dict())
    # Si le formsemestre existe on l'ajoute au dictionnaire
    if formsemestre:
        data["formsemestre"] = {
            "id": formsemestre.id,
            "title": formsemestre.session_id(),
        }
    return data


@bp.route(
    "/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
)
@api_web_bp.route(
    "/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
)
@bp.route(
    "/justificatifs/formsemestre/<int:formsemestre_id>/query",
    defaults={"with_query": True},
)
@api_web_bp.route(
    "/justificatifs/formsemestre/<int:formsemestre_id>/query",
    defaults={"with_query": True},
)
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
    """Retourne tous les justificatifs du formsemestre"""

    # Récupération du formsemestre
    formsemestre: FormSemestre = None
    formsemestre_id = int(formsemestre_id)
    formsemestre: FormSemestre = FormSemestre.query.filter_by(
        id=formsemestre_id
    ).first()

    if formsemestre is None:
        return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")

    # Récupération des justificatifs du semestre
    justificatifs_query: Query = scass.filter_by_formsemestre(
        Justificatif.query, Justificatif, formsemestre
    )

    # Filtrage des justificatifs
    if with_query:
        justificatifs_query: Query = _filter_manager(request, justificatifs_query)

    # Retour des justificatifs en JSON
    data_set: list[dict] = []
    for justi in justificatifs_query.all():
        data = justi.to_dict(format_api=True)
        data_set.append(data)

    return data_set


@bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@bp.route("/justificatif/etudid/<int:etudid>/create", methods=["POST"])
@api_web_bp.route("/justificatif/etudid/<int:etudid>/create", methods=["POST"])
# nip
@bp.route("/justificatif/nip/<nip>/create", methods=["POST"])
@api_web_bp.route("/justificatif/nip/<nip>/create", methods=["POST"])
# ine
@bp.route("/justificatif/ine/<ine>/create", methods=["POST"])
@api_web_bp.route("/justificatif/ine/<ine>/create", methods=["POST"])
@scodoc
@login_required
@as_json
@permission_required(Permission.AbsChange)
def justif_create(etudid: int = None, nip=None, ine=None):
    """
    Création d'un justificatif pour l'étudiant (etudid)
    La requête doit avoir un content type "application/json":
    [
        {
            "date_debut": str,
            "date_fin": str,
            "etat": str,
        },
        {
            "date_debut": str,
            "date_fin": str,
            "etat": str,
            "raison":str,
        }
        ...
    ]

    """

    # Récupération de l'étudiant
    etud: Identite = tools.get_etud(etudid, nip, ine)

    if etud is None:
        return json_error(
            404,
            message="étudiant inconnu",
        )

    # Récupération des justificatifs à créer
    create_list: list[object] = request.get_json(force=True)

    if not isinstance(create_list, list):
        return json_error(404, "Le contenu envoyé n'est pas une liste")

    errors: list[dict] = []
    success: list[dict] = []
    justifs: list[Justificatif] = []

    # énumération des justificatifs
    for i, data in enumerate(create_list):
        code, obj, justi = _create_one(data, etud)
        code: int
        obj: str | dict
        justi: Justificatif | None
        if code == 404:
            errors.append({"indice": i, "message": obj})
        else:
            success.append({"indice": i, "message": obj})
            justifs.append(justi)
            scass.simple_invalidate_cache(data, etud.id)

    # Actualisation des assiduités justifiées en fonction de tous les nouveaux justificatifs
    compute_assiduites_justified(etud.etudid, justifs)
    return {"errors": errors, "success": success}


def _create_one(
    data: dict,
    etud: Identite,
) -> tuple[int, object, Justificatif]:
    errors: list[str] = []

    # -- vérifications de l'objet json --
    # cas 1 : ETAT
    etat: str = data.get("etat", None)
    if etat is None:
        errors.append("param 'etat': manquant")
    elif not scu.EtatJustificatif.contains(etat):
        errors.append("param 'etat': invalide")

    etat: scu.EtatJustificatif = scu.EtatJustificatif.get(etat)

    # cas 2 : date_debut
    date_debut: str = data.get("date_debut", None)
    if date_debut is None:
        errors.append("param 'date_debut': manquant")
    deb: datetime = scu.is_iso_formated(date_debut, convert=True)
    if deb is None:
        errors.append("param 'date_debut': format invalide")

    # cas 3 : date_fin
    date_fin: str = data.get("date_fin", None)
    if date_fin is None:
        errors.append("param 'date_fin': manquant")
    fin: datetime = scu.is_iso_formated(date_fin, convert=True)
    if fin is None:
        errors.append("param 'date_fin': format invalide")

    # cas 4 : raison

    raison: str = data.get("raison", None)

    external_data: dict = data.get("external_data")
    if external_data is not None:
        if not isinstance(external_data, dict):
            errors.append("param 'external_data' : n'est pas un objet JSON")

    if errors:
        err: str = ", ".join(errors)
        return (404, err, None)

    # TOUT EST OK

    try:
        # On essaye de créer le justificatif
        nouv_justificatif: Query = Justificatif.create_justificatif(
            date_debut=deb,
            date_fin=fin,
            etat=etat,
            etudiant=etud,
            raison=raison,
            user_id=current_user.id,
            external_data=external_data,
        )

        # Si tout s'est bien passé on ajoute l'assiduité à la session
        # et on retourne un code 200 avec un objet possèdant le justif_id
        # ainsi que les assiduités justifiées par le dit justificatif

        # On renvoie aussi le justificatif créé pour pour le calcul total de fin
        db.session.add(nouv_justificatif)
        db.session.commit()

        return (
            200,
            {
                "justif_id": nouv_justificatif.id,
                "couverture": scass.justifies(nouv_justificatif),
            },
            nouv_justificatif,
        )
    except ScoValueError as excp:
        return (404, excp.args[0], None)


@bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
@api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
@login_required
@scodoc
@as_json
@permission_required(Permission.AbsChange)
def justif_edit(justif_id: int):
    """
    Edition d'un justificatif à partir de son id
    La requête doit avoir un content type "application/json":

    {
        "etat"?: str,
        "raison"?: str
        "date_debut"?: str
        "date_fin"?: str
    }
    """

    # Récupération du justificatif à modifier
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    errors: list[str] = []
    data = request.get_json(force=True)

    # Récupération des assiduités (id) précédemment justifiée par le justificatif
    avant_ids: list[int] = scass.justifies(justificatif_unique)
    # Vérifications de data

    # Cas 1 : Etat
    if data.get("etat") is not None:
        etat: scu.EtatJustificatif = scu.EtatJustificatif.get(data.get("etat"))
        if etat is None:
            errors.append("param 'etat': invalide")
        else:
            justificatif_unique.etat = etat

    # Cas 2 : raison
    raison: str = data.get("raison", False)
    if raison is not False:
        justificatif_unique.raison = raison

    deb, fin = None, None

    # cas 3 : date_debut
    date_debut: str = data.get("date_debut", False)
    if date_debut is not False:
        if date_debut is None:
            errors.append("param 'date_debut': manquant")
        deb: datetime = scu.is_iso_formated(date_debut.replace(" ", "+"), convert=True)
        if deb is None:
            errors.append("param 'date_debut': format invalide")

    # cas 4 : date_fin
    date_fin: str = data.get("date_fin", False)
    if date_fin is not False:
        if date_fin is None:
            errors.append("param 'date_fin': manquant")
        fin: datetime = scu.is_iso_formated(date_fin.replace(" ", "+"), convert=True)
        if fin is None:
            errors.append("param 'date_fin': format invalide")

    # Récupération des dates précédentes si deb ou fin est None
    deb = deb if deb is not None else justificatif_unique.date_debut
    fin = fin if fin is not None else justificatif_unique.date_fin

    # Mise à jour de l'external data
    external_data: dict = data.get("external_data")
    if external_data is not None:
        if not isinstance(external_data, dict):
            errors.append("param 'external_data' : n'est pas un objet JSON")
        else:
            justificatif_unique.external_data = external_data

    if fin <= deb:
        errors.append("param 'dates' : Date de début après date de fin")

    # Mise à jour des dates du justificatif
    justificatif_unique.date_debut = deb
    justificatif_unique.date_fin = fin

    if errors:
        err: str = ", ".join(errors)
        return json_error(404, err)

    # Mise à jour du justificatif
    db.session.add(justificatif_unique)
    db.session.commit()

    # Génération du dictionnaire de retour
    # La couverture correspond
    # - aux assiduités précédemment justifiées par le justificatif
    # - aux assiduités qui sont justifiées par le justificatif modifié
    retour = {
        "couverture": {
            "avant": avant_ids,
            "apres": compute_assiduites_justified(
                justificatif_unique.etudid,
                [justificatif_unique],
                True,
            ),
        }
    }
    # Invalide le cache
    scass.simple_invalidate_cache(justificatif_unique.to_dict())
    return retour


@bp.route("/justificatif/delete", methods=["POST"])
@api_web_bp.route("/justificatif/delete", methods=["POST"])
@login_required
@scodoc
@as_json
@permission_required(Permission.AbsChange)
def justif_delete():
    """
    Suppression d'un justificatif à partir de son id

    Forme des données envoyées :

    [
        <justif_id:int>,
        ...
    ]


    """

    # Récupération des justif_ids
    justificatifs_list: list[int] = request.get_json(force=True)
    if not isinstance(justificatifs_list, list):
        return json_error(404, "Le contenu envoyé n'est pas une liste")

    output = {"errors": [], "success": []}

    for i, ass in enumerate(justificatifs_list):
        code, msg = _delete_one(ass)
        if code == 404:
            output["errors"].append({"indice": i, "message": msg})
        else:
            output["success"].append({"indice": i, "message": "OK"})

    db.session.commit()

    return output


def _delete_one(justif_id: int) -> tuple[int, str]:
    """
    _delete_one Supprime un justificatif

    Args:
        justif_id (int): l'identifiant du justificatif

    Returns:
        tuple[int, str]: code, message
            code : 200 si réussi, 404 sinon
            message : OK si réussi, message d'erreur sinon
    """
    # Récupération du justificatif à supprimer
    try:
        justificatif_unique = Justificatif.get_justificatif(justif_id)
    except NotFound:
        return (404, "Justificatif non existant")
    # Récupération de l'archive du justificatif
    archive_name: str = justificatif_unique.fichier

    if archive_name is not None:
        # Si elle existe : on essaye de la supprimer
        archiver: JustificatifArchiver = JustificatifArchiver()
        try:
            archiver.delete_justificatif(justificatif_unique.etudiant, archive_name)
        except ValueError:
            pass

    # On invalide le cache
    scass.simple_invalidate_cache(justificatif_unique.to_dict())
    # On supprime le justificatif
    db.session.delete(justificatif_unique)
    # On actualise les assiduités justifiées de l'étudiant concerné
    compute_assiduites_justified(
        justificatif_unique.etudid,
        Justificatif.query.filter_by(etudid=justificatif_unique.etudid).all(),
        True,
    )

    return (200, "OK")


# Partie archivage
@bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
@api_web_bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
@scodoc
@login_required
@as_json
@permission_required(Permission.AbsChange)
def justif_import(justif_id: int = None):
    """
    Importation d'un fichier (création d'archive)
    """

    # On vérifie qu'un fichier a bien été envoyé
    if len(request.files) == 0:
        return json_error(404, "Il n'y a pas de fichier joint")
    file = list(request.files.values())[0]
    if file.filename == "":
        return json_error(404, "Il n'y a pas de fichier joint")

    # On récupère le justificatif auquel on va importer le fichier
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    # Récupération de l'archive si elle existe
    archive_name: str = justificatif_unique.fichier

    # Utilisation de l'archiver de justificatifs
    archiver: JustificatifArchiver = JustificatifArchiver()
    try:
        # On essaye de sauvegarder le fichier
        fname: str
        archive_name, fname = archiver.save_justificatif(
            justificatif_unique.etudiant,
            filename=file.filename,
            data=file.stream.read(),
            archive_name=archive_name,
            user_id=current_user.id,
        )

        # On actualise l'archive du justificatif
        justificatif_unique.fichier = archive_name

        db.session.add(justificatif_unique)
        db.session.commit()

        return {"filename": fname}
    except ScoValueError as exc:
        # Si cela ne fonctionne pas on renvoie une erreur
        return json_error(404, exc.args[0])


@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
@api_web_bp.route(
    "/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"]
)
@scodoc
@login_required
@permission_required(Permission.ScoView)
def justif_export(justif_id: int | None = None, filename: str | None = None):
    """
    Retourne un fichier d'une archive d'un justificatif.
    La permission est ScoView + (AbsJustifView ou être l'auteur du justifcatif)
    """
    # On récupère le justificatif concerné
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    # Vérification des permissions
    if not (
        current_user.has_permission(Permission.AbsJustifView)
        or justificatif_unique.user_id == current_user.id
    ):
        return json_error(401, "non autorisé à voir ce fichier")

    # On récupère l'archive concernée
    archive_name: str = justificatif_unique.fichier
    if archive_name is None:
        # On retourne une erreur si le justificatif n'a pas de fichiers
        return json_error(404, "le justificatif ne possède pas de fichier")

    # On récupère le fichier et le renvoie en une réponse déjà formée
    archiver: JustificatifArchiver = JustificatifArchiver()
    try:
        return archiver.get_justificatif_file(
            archive_name, justificatif_unique.etudiant, filename
        )
    except ScoValueError as err:
        # On retourne une erreur json si jamais il y a un problème
        return json_error(404, err.args[0])


@bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
@api_web_bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
@scodoc
@login_required
@as_json
@permission_required(Permission.AbsChange)
def justif_remove(justif_id: int = None):
    # XXX TODO pas de test unitaire
    """
    Supression d'un fichier ou d'une archive
    {
        "remove": <"all"/"list">

        "filenames"?: [
            <filename:str>,
            ...
        ]
    }
    """

    # On récupère le dictionnaire
    data: dict = request.get_json(force=True)

    # On récupère le justificatif concerné
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    # On récupère l'archive
    archive_name: str = justificatif_unique.fichier
    if archive_name is None:
        # On retourne une erreur si le justificatif n'a pas de fichiers
        return json_error(404, "le justificatif ne possède pas de fichier")

    # On regarde le type de suppression (all ou list)
    # Si all : on supprime tous les fichiers
    # Si list : on supprime les fichiers dont le nom est dans la liste
    remove: str = data.get("remove")
    if remove is None or remove not in ("all", "list"):
        return json_error(404, "param 'remove': Valeur invalide")

    # On récupère l'archiver et l'étudiant
    archiver: JustificatifArchiver = JustificatifArchiver()
    etud = justificatif_unique.etudiant
    try:
        if remove == "all":
            # Suppression de toute l'archive du justificatif
            archiver.delete_justificatif(etud, archive_name=archive_name)
            justificatif_unique.fichier = None
            db.session.add(justificatif_unique)
            db.session.commit()

        else:
            # Suppression des fichiers dont le nom se trouve dans la liste "filenames"
            for fname in data.get("filenames", []):
                archiver.delete_justificatif(
                    etud,
                    archive_name=archive_name,
                    filename=fname,
                )

            # Si il n'y a plus de fichiers dans l'archive, on la supprime
            if len(archiver.list_justificatifs(archive_name, etud)) == 0:
                archiver.delete_justificatif(etud, archive_name)
                justificatif_unique.fichier = None
                db.session.add(justificatif_unique)
                db.session.commit()

    except ScoValueError as err:
        # On retourne une erreur json si jamais il y a eu un problème
        return json_error(404, err.args[0])

    # On retourne une réponse "removed" si tout s'est bien passé
    return {"response": "removed"}


@bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
@api_web_bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
@scodoc
@login_required
@as_json
@permission_required(Permission.ScoView)
def justif_list(justif_id: int = None):
    """
    Liste les fichiers du justificatif
    """

    # Récupération du justificatif concerné
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    # Récupération de l'archive avec l'archiver
    archive_name: str = justificatif_unique.fichier
    filenames: list[str] = []
    archiver: JustificatifArchiver = JustificatifArchiver()
    if archive_name is not None:
        filenames = archiver.list_justificatifs(
            archive_name, justificatif_unique.etudiant
        )
    # Préparation du retour
    # - total : le nombre total de fichier du justificatif
    # - filenames : le nom des fichiers visible par l'utilisateur
    retour = {"total": len(filenames), "filenames": []}

    # Pour chaque nom de fichier on vérifie
    # - Si l'utilisateur qui a importé le fichier est le même que
    #   l'utilisateur qui a demandé la liste des fichiers
    # - Ou si l'utilisateur qui a demandé la liste possède la permission AbsJustifView
    # Si c'est le cas alors on ajoute à la liste des fichiers visibles
    for filename in filenames:
        if int(filename[1]) == current_user.id or current_user.has_permission(
            Permission.AbsJustifView
        ):
            retour["filenames"].append(filename[0])
    # On renvoie le total et la liste des fichiers visibles
    return retour


# Partie justification
@bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
@api_web_bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
@scodoc
@login_required
@as_json
@permission_required(Permission.AbsChange)
def justif_justifies(justif_id: int = None):
    """
    Liste assiduite_id justifiées par le justificatif
    """

    # On récupère le justificatif concerné
    justificatif_unique = Justificatif.get_justificatif(justif_id)

    # On récupère la liste des assiduités justifiées par le justificatif
    assiduites_list: list[int] = scass.justifies(justificatif_unique)
    # On la renvoie
    return assiduites_list


# -- Utils --


def _filter_manager(requested, justificatifs_query: Query):
    """
    Retourne les justificatifs entrés filtrés en fonction de la request
    et du département courant s'il y en a un
    """
    # cas 1 : etat justificatif
    etat: str = requested.args.get("etat")
    if etat is not None:
        justificatifs_query: Query = scass.filter_justificatifs_by_etat(
            justificatifs_query, etat
        )

    # cas 2 : date de début
    deb: str = requested.args.get("date_debut", "").replace(" ", "+")
    deb: datetime = scu.is_iso_formated(deb, True)

    # cas 3 : date de fin
    fin: str = requested.args.get("date_fin", "").replace(" ", "+")
    fin: datetime = scu.is_iso_formated(fin, True)

    if (deb, fin) != (None, None):
        justificatifs_query: Query = scass.filter_by_date(
            justificatifs_query, Justificatif, deb, fin
        )
    # cas 4 : user_id
    user_id = requested.args.get("user_id", False)
    if user_id is not False:
        justificatifs_query: Query = scass.filter_by_user_id(
            justificatifs_query, user_id
        )

    # cas 5 : formsemestre_id
    formsemestre_id = requested.args.get("formsemestre_id")

    if formsemestre_id not in [None, "", -1]:
        formsemestre: FormSemestre = None
        try:
            formsemestre_id = int(formsemestre_id)
            formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
            justificatifs_query = scass.filter_by_formsemestre(
                justificatifs_query, Justificatif, formsemestre
            )
        except ValueError:
            formsemestre = None

    # cas 6 : order (retourne les justificatifs par ordre décroissant de date_debut)
    order = requested.args.get("order", None)
    if order is not None:
        justificatifs_query: Query = justificatifs_query.order_by(
            Justificatif.date_debut.desc()
        )
    # cas 7 : courant (retourne uniquement les justificatifs de l'année scolaire courante)
    courant = requested.args.get("courant", None)
    if courant is not None:
        annee: int = scu.annee_scolaire()

        justificatifs_query: Query = justificatifs_query.filter(
            Justificatif.date_debut >= scu.date_debut_annee_scolaire(annee),
            Justificatif.date_fin <= scu.date_fin_annee_scolaire(annee),
        )

    # cas 8 : group_id filtre les justificatifs d'un groupe d'étudiant
    group_id = requested.args.get("group_id", None)
    if group_id is not None:
        try:
            group_id = int(group_id)
            etudids: list[int] = [etu["etudid"] for etu in get_group_members(group_id)]
            justificatifs_query = justificatifs_query.filter(
                Justificatif.etudid.in_(etudids)
            )
        except ValueError:
            group_id = None

    # Département
    if g.scodoc_dept:
        justificatifs_query = justificatifs_query.join(Identite).filter_by(
            dept_id=g.scodoc_dept_id
        )

    return justificatifs_query
