##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################

"""
  ScoDoc 9 API : accès aux formsemestres
"""
from operator import attrgetter, itemgetter

from flask import g, make_response, request
from flask_json import as_json
from flask_login import login_required

import app
from app import db
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
from app.decorators import scodoc, permission_required
from app.scodoc.sco_utils import json_error
from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_compat import NotesTableCompat
from app.models import (
    Departement,
    Evaluation,
    FormSemestre,
    FormSemestreEtape,
    FormSemestreInscription,
    Identite,
    ModuleImpl,
    NotesNotes,
)
from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
from app.scodoc import sco_groups
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import ModuleType
import app.scodoc.sco_utils as scu
from app.tables.recap import TableRecap


@bp.route("/formsemestre/<int:formsemestre_id>")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_infos(formsemestre_id: int):
    """
    Information sur le formsemestre indiqué.

    formsemestre_id : l'id du formsemestre

    Exemple de résultat :
        {
          "block_moyennes": false,
          "bul_bgcolor": "white",
          "bul_hide_xml": false,
          "date_debut_iso": "2021-09-01",
          "date_debut": "01/09/2021",
          "date_fin_iso": "2022-08-31",
          "date_fin": "31/08/2022",
          "dept_id": 1,
          "elt_annee_apo": null,
          "elt_sem_apo": null,
          "ens_can_edit_eval": false,
          "etat": true,
          "formation_id": 1,
          "formsemestre_id": 1,
          "gestion_compensation": false,
          "gestion_semestrielle": false,
          "id": 1,
          "modalite": "FI",
          "resp_can_change_ens": true,
          "resp_can_edit": false,
          "responsables": [1, 99], // uids
          "scodoc7_id": null,
          "semestre_id": 1,
          "titre_formation" : "BUT GEA",
          "titre_num": "BUT GEA semestre 1",
          "titre": "BUT GEA",
        }

    """
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    return formsemestre.to_dict_api()


@bp.route("/formsemestres/query")
@api_web_bp.route("/formsemestres/query")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestres_query():
    """
    Retourne les formsemestres filtrés par
     étape Apogée ou année scolaire ou département (acronyme ou id) ou état ou code étudiant

    etape_apo : un code étape apogée
    annee_scolaire : année de début de l'année scolaire
    dept_acronym : acronyme du département (eg "RT")
    dept_id : id du département
    ine ou nip: code d'un étudiant: ramène alors tous les semestres auxquels il est inscrit.
    etat: 0 si verrouillé, 1 sinon
    """
    etape_apo = request.args.get("etape_apo")
    annee_scolaire = request.args.get("annee_scolaire")
    dept_acronym = request.args.get("dept_acronym")
    dept_id = request.args.get("dept_id")
    etat = request.args.get("etat")
    nip = request.args.get("nip")
    ine = request.args.get("ine")
    formsemestres = FormSemestre.query
    if g.scodoc_dept:
        formsemestres = formsemestres.filter_by(dept_id=g.scodoc_dept_id)
    if annee_scolaire is not None:
        try:
            annee_scolaire_int = int(annee_scolaire)
        except ValueError:
            return json_error(API_CLIENT_ERROR, "invalid annee_scolaire: not int")
        debut_annee = scu.date_debut_anne_scolaire(annee_scolaire_int)
        fin_annee = scu.date_fin_anne_scolaire(annee_scolaire_int)
        formsemestres = formsemestres.filter(
            FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
        )
    if etat is not None:
        try:
            etat = bool(int(etat))
        except ValueError:
            return json_error(404, "invalid etat: integer expected")
        formsemestres = formsemestres.filter_by(etat=etat)
    if dept_acronym is not None:
        formsemestres = formsemestres.join(Departement).filter_by(acronym=dept_acronym)
    if dept_id is not None:
        try:
            dept_id = int(dept_id)
        except ValueError:
            return json_error(404, "invalid dept_id: integer expected")
        formsemestres = formsemestres.filter_by(dept_id=dept_id)
    if etape_apo is not None:
        formsemestres = formsemestres.join(FormSemestreEtape).filter(
            FormSemestreEtape.etape_apo == etape_apo
        )
    inscr_joined = False
    if nip is not None:
        formsemestres = (
            formsemestres.join(FormSemestreInscription)
            .join(Identite)
            .filter_by(code_nip=nip)
        )
        inscr_joined = True
    if ine is not None:
        if not inscr_joined:
            formsemestres = formsemestres.join(FormSemestreInscription).join(Identite)
        formsemestres = formsemestres.filter_by(code_ine=ine)

    return [
        formsemestre.to_dict_api()
        for formsemestre in formsemestres.order_by(
            FormSemestre.date_debut.desc(),
            FormSemestre.modalite,
            FormSemestre.semestre_id,
            FormSemestre.titre,
        )
    ]


@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def bulletins(formsemestre_id: int, version: str = "long"):
    """
    Retourne les bulletins d'un formsemestre donné

    formsemestre_id : l'id d'un formesemestre

    Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin
    """
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first()
    if formsemestre is None:
        return json_error(404, "formsemestre non trouve")
    app.set_sco_dept(formsemestre.departement.acronym)

    data = []
    for etu in formsemestre.etuds:
        bul_etu = get_formsemestre_bulletin_etud_json(
            formsemestre, etu, version=version
        )
        data.append(bul_etu.json)

    return data


@bp.route("/formsemestre/<int:formsemestre_id>/programme")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/programme")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_programme(formsemestre_id: int):
    """
    Retourne la liste des UEs, ressources et SAEs d'un semestre

    formsemestre_id : l'id d'un formsemestre

    Exemple de résultat :
        {
          "ues": [
            {
              "type": 0,
              "formation_id": 1,
              "ue_code": "UCOD11",
              "id": 1,
              "ects": 12.0,
              "acronyme": "RT1.1",
              "is_external": false,
              "numero": 1,
              "code_apogee": "",
              "titre": "Administrer les r\u00e9seaux et l\u2019Internet",
              "coefficient": 0.0,
              "semestre_idx": 1,
              "color": "#B80004",
              "ue_id": 1
            },
            ...
          ],
          "ressources": [
            {
            "ens": [ 10, 18 ],
            "formsemestre_id": 1,
            "id": 15,
            "module": {
                "abbrev": "Programmer",
                "code": "SAE15",
                "code_apogee": "V7GOP",
                "coefficient": 1.0,
                "formation_id": 1,
                "heures_cours": 0.0,
                "heures_td": 0.0,
                "heures_tp": 0.0,
                "id": 15,
                "matiere_id": 3,
                "module_id": 15,
                "module_type": 3,
                "numero": 50,
                "semestre_id": 1,
                "titre": "Programmer en Python",
                "ue_id": 3
            },
            "module_id": 15,
            "moduleimpl_id": 15,
            "responsable_id": 2
          },
            ...
          ],
          "saes": [
            {
              ...
            },
            ...
          ],
          "modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
        }
    """
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    ues = formsemestre.get_ues()
    m_list = {
        ModuleType.RESSOURCE: [],
        ModuleType.SAE: [],
        ModuleType.STANDARD: [],
        ModuleType.MALUS: [],
    }
    for modimpl in formsemestre.modimpls_sorted:
        d = modimpl.to_dict(convert_objects=True)
        m_list[modimpl.module.module_type].append(d)
    return {
        "ues": [ue.to_dict(convert_objects=True) for ue in ues],
        "ressources": m_list[ModuleType.RESSOURCE],
        "saes": m_list[ModuleType.SAE],
        "modules": m_list[ModuleType.STANDARD],
        "malus": m_list[ModuleType.MALUS],
    }


@bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants",
    defaults={"with_query": False, "long": False},
)
@bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/long",
    defaults={"with_query": False, "long": True},
)
@bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/query",
    defaults={"with_query": True, "long": False},
)
@bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/long/query",
    defaults={"with_query": True, "long": True},
)
@api_web_bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants",
    defaults={"with_query": False, "long": False},
)
@api_web_bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/long",
    defaults={"with_query": False, "long": True},
)
@api_web_bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/query",
    defaults={"with_query": True, "long": False},
)
@api_web_bp.route(
    "/formsemestre/<int:formsemestre_id>/etudiants/long/query",
    defaults={"with_query": True, "long": True},
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_etudiants(
    formsemestre_id: int, with_query: bool = False, long: bool = False
):
    """Étudiants d'un formsemestre."""
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    if with_query:
        etat = request.args.get("etat")
        if etat is not None:
            etat = {
                "actifs": scu.INSCRIT,
                "demissionnaires": scu.DEMISSION,
                "defaillants": scu.DEF,
            }.get(etat, etat)
            inscriptions = [
                ins for ins in formsemestre.inscriptions if ins.etat == etat
            ]
        else:
            inscriptions = formsemestre.inscriptions
    else:
        inscriptions = formsemestre.inscriptions

    if long:
        etuds = [ins.etud.to_dict_api() for ins in inscriptions]
    else:
        etuds = [ins.etud.to_dict_short() for ins in inscriptions]
    # Ajout des groupes de chaque étudiants
    # XXX A REVOIR: trop inefficace !
    for etud in etuds:
        etud["groups"] = sco_groups.get_etud_groups(
            etud["id"], formsemestre_id, exclude_default=True
        )

    return sorted(etuds, key=itemgetter("sort_key"))


@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def etat_evals(formsemestre_id: int):
    """
    Informations sur l'état des évaluations d'un formsemestre.

    formsemestre_id : l'id d'un semestre

    Exemple de résultat :
    [
      {
        "id": 1, // moduleimpl_id
        "titre": "Initiation aux réseaux informatiques",
        "evaluations": [
          {
            "id": 1,
            "description": null,
            "datetime_epreuve": null,
            "heure_fin": "09:00:00",
            "coefficient": "02.00"
            "is_complete": true,
            "nb_inscrits": 16,
            "nb_manquantes": 0,
            "ABS": 0,
            "ATT": 0,
            "EXC": 0,
            "saisie_notes": {
              "datetime_debut": "2021-09-11T00:00:00+02:00",
              "datetime_fin": "2022-08-25T00:00:00+02:00",
              "datetime_mediane": "2022-03-19T00:00:00+01:00"
            }
          },
          ...
        ]
      },
    ]
    """
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    app.set_sco_dept(formsemestre.departement.acronym)
    nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)

    result = []
    for modimpl_id in nt.modimpls_results:
        modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
        modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
        modimpl_dict = modimpl.to_dict(convert_objects=True)

        list_eval = []
        for evaluation_id in modimpl_results.evaluations_etat:
            eval_etat = modimpl_results.evaluations_etat[evaluation_id]
            evaluation = Evaluation.query.get_or_404(evaluation_id)
            eval_dict = evaluation.to_dict_api()
            eval_dict["etat"] = eval_etat.to_dict()

            eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
            eval_dict["nb_notes_manquantes"] = len(
                modimpl_results.evals_etudids_sans_note[evaluation.id]
            )
            eval_dict["nb_notes_abs"] = sum(
                modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
            )
            eval_dict["nb_notes_att"] = eval_etat.nb_attente
            eval_dict["nb_notes_exc"] = sum(
                modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
            )

            # Récupération de toutes les notes de l'évaluation
            # eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)

            notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()

            date_debut = None
            date_fin = None
            date_mediane = None

            # Si il y a plus d'une note saisie pour l'évaluation
            if len(notes) >= 1:
                # Tri des notes en fonction de leurs dates
                notes_sorted = sorted(notes, key=attrgetter("date"))

                date_debut = notes_sorted[0].date
                date_fin = notes_sorted[-1].date

                # Note médiane
                date_mediane = notes_sorted[len(notes_sorted) // 2].date

            eval_dict["saisie_notes"] = {
                "datetime_debut": date_debut.isoformat()
                if date_debut is not None
                else None,
                "datetime_fin": date_fin.isoformat() if date_fin is not None else None,
                "datetime_mediane": date_mediane.isoformat()
                if date_mediane is not None
                else None,
            }

            list_eval.append(eval_dict)

        modimpl_dict["evaluations"] = list_eval
        result.append(modimpl_dict)
    return result


@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/resultats")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_resultat(formsemestre_id: int):
    """Tableau récapitulatif des résultats
    Pour chaque étudiant, son état, ses groupes, ses moyennes d'UE et de modules.
    """
    format_spec = request.args.get("format", None)
    if format_spec is not None and format_spec != "raw":
        return json_error(API_CLIENT_ERROR, "invalid format specification")
    convert_values = format_spec != "raw"

    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    app.set_sco_dept(formsemestre.departement.acronym)
    res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
    table = TableRecap(
        res, convert_values=convert_values, include_evaluations=False, mode_jury=False
    )
    # Supprime les champs inutiles (mise en forme)
    rows = table.to_list()
    # Ajoute le groupe de chaque partition:
    etud_groups = sco_groups.get_formsemestre_etuds_groups(formsemestre_id)
    for row in rows:
        row["partitions"] = etud_groups.get(row["etudid"], {})

    return rows


@bp.route("/formsemestre/<int:formsemestre_id>/get_groups_auto_assignment")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/get_groups_auto_assignment")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def get_groups_auto_assignment(formsemestre_id: int):
    """rend les données"""
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
    response = make_response(formsemestre.groups_auto_assignment_data or b"")
    response.headers["Content-Type"] = scu.JSON_MIMETYPE
    return response


@bp.route(
    "/formsemestre/<int:formsemestre_id>/save_groups_auto_assignment", methods=["POST"]
)
@api_web_bp.route(
    "/formsemestre/<int:formsemestre_id>/save_groups_auto_assignment", methods=["POST"]
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def save_groups_auto_assignment(formsemestre_id: int):
    """enregistre les données"""
    query = FormSemestre.query.filter_by(id=formsemestre_id)
    if g.scodoc_dept:
        query = query.filter_by(dept_id=g.scodoc_dept_id)
    formsemestre: FormSemestre = query.first_or_404(formsemestre_id)

    if len(request.data) > GROUPS_AUTO_ASSIGNMENT_DATA_MAX:
        return json_error(413, "data too large")
    formsemestre.groups_auto_assignment_data = request.data
    db.session.add(formsemestre)
    db.session.commit()