# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@gmail.com
#
##############################################################################

"""Gestion évaluations (ScoDoc7, code en voie de modernisation)
"""

import flask
from flask import url_for, g
from flask_login import current_user
import sqlalchemy as sa

from app import db, log

from app.models import Evaluation
from app.models.evaluations import check_convert_evaluation_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied

from app.scodoc import sco_cache
from app.scodoc import sco_moduleimpl


_evaluationEditor = ndb.EditableTable(
    "notes_evaluation",
    "evaluation_id",
    (
        "evaluation_id",
        "moduleimpl_id",
        "date_debut",
        "date_fin",
        "description",
        "note_max",
        "coefficient",
        "visibulletin",
        "publish_incomplete",
        "evaluation_type",
        "numero",
    ),
    sortkey="numero, date_debut desc",  # plus recente d'abord
    output_formators={
        "numero": ndb.int_null_is_zero,
    },
    input_formators={
        "visibulletin": bool,
        "publish_incomplete": bool,
        "evaluation_type": int,
    },
)


def get_evaluations_dict(args: dict) -> list[dict]:
    """Liste evaluations, triées numero (or most recent date first).
    Fonction de transition pour ancien code ScoDoc7.

    Ajoute les champs:
    'duree' : '2h30'
    'matin' : 1 (commence avant 12:00) ou 0
    'apresmidi' : 1 (termine après 12:00) ou 0
    'descrheure' : ' de 15h00 à 16h30'
    """
    # calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
    return [
        e.to_dict()
        for e in Evaluation.query.filter_by(**args).order_by(
            sa.desc(Evaluation.numero), sa.desc(Evaluation.date_debut)
        )
    ]


def do_evaluation_list_in_formsemestre(formsemestre_id):
    "list evaluations in this formsemestre"
    mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
    evals = []
    for modimpl in mods:
        evals += get_evaluations_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
    return evals


def do_evaluation_edit(args):
    "edit an evaluation"
    evaluation_id = args["evaluation_id"]
    evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
    if evaluation is None:
        raise ValueError("evaluation inexistante !")

    if not evaluation.moduleimpl.can_edit_evaluation(current_user):
        raise AccessDenied(
            f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
        )
    args["moduleimpl_id"] = evaluation.moduleimpl.id
    check_convert_evaluation_args(evaluation.moduleimpl, args)

    cnx = ndb.GetDBConnexion()
    _evaluationEditor.edit(cnx, args)
    # inval cache pour ce semestre
    sco_cache.invalidate_formsemestre(
        formsemestre_id=evaluation.moduleimpl.formsemestre_id
    )


# ancien _notes_getall
def do_evaluation_get_all_notes(
    evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None
):
    """Toutes les notes pour une évaluation: { etudid : { 'value' : value, 'date' : date ... }}
    Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module.
    """
    # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
    do_cache = filter_suppressed and table == "notes_notes" and (by_uid is None)
    if do_cache:
        r = sco_cache.EvaluationCache.get(evaluation_id)
        if r is not None:
            return r
    cnx = ndb.GetDBConnexion()
    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
    cond = " where evaluation_id=%(evaluation_id)s"
    if by_uid:
        cond += " and uid=%(by_uid)s"

    cursor.execute(
        "select * from " + table + cond,
        {"evaluation_id": evaluation_id, "by_uid": by_uid},
    )
    res = cursor.dictfetchall()
    d = {}
    if filter_suppressed:
        for x in res:
            if x["value"] != scu.NOTES_SUPPRESS:
                d[x["etudid"]] = x
    else:
        for x in res:
            d[x["etudid"]] = x
    if do_cache:
        status = sco_cache.EvaluationCache.set(evaluation_id, d)
        if not status:
            log(f"Warning: EvaluationCache.set: {evaluation_id}\t{status}")
    return d


def moduleimpl_evaluation_move(evaluation_id: int, after=0):
    """Move before/after previous one (decrement/increment numero)
    (published)
    """
    evaluation = Evaluation.get_evaluation(evaluation_id)
    # access: can change eval ?
    if not evaluation.moduleimpl.can_edit_evaluation(current_user):
        raise AccessDenied(
            f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
        )
    after = int(after)  # 0: deplace avant, 1 deplace apres
    if after not in (0, 1):
        raise ValueError('invalid value for "after"')

    Evaluation.moduleimpl_evaluation_renumber(
        evaluation.moduleimpl, only_if_unumbered=True
    )
    mod_evaluations = evaluation.moduleimpl.evaluations.all()
    if len(mod_evaluations) > 1:
        idx = [e.id for e in mod_evaluations].index(evaluation.id)
        neigh = None  # object to swap with
        if after == 0 and idx > 0:
            neigh = mod_evaluations[idx - 1]
        elif after == 1 and idx < len(mod_evaluations) - 1:
            neigh = mod_evaluations[idx + 1]
        if neigh:  #
            if neigh.numero == evaluation.numero:
                log("Warning: moduleimpl_evaluation_move: forcing renumber")
                Evaluation.moduleimpl_evaluation_renumber(
                    evaluation.moduleimpl, only_if_unumbered=False
                )
            else:
                # swap numero with neighbor
                evaluation.numero, neigh.numero = neigh.numero, evaluation.numero
                db.session.add(evaluation)
                db.session.add(neigh)
                db.session.commit()
    # redirect to moduleimpl page:
    return flask.redirect(
        url_for(
            "notes.moduleimpl_status",
            scodoc_dept=g.scodoc_dept,
            moduleimpl_id=evaluation.moduleimpl.id,
        )
    )