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

"""
  ScoDoc 9 API : jury   WIP à compléter avec enregistrement décisions
"""

import datetime

from flask import g, request, url_for
from flask_json import as_json
from flask_login import current_user, login_required

import app
from app import db, log
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR, tools
from app.decorators import scodoc, permission_required
from app.scodoc.sco_exceptions import ScoException
from app.but import jury_but_results
from app.models import (
    ApcParcours,
    ApcValidationAnnee,
    ApcValidationRCUE,
    Formation,
    FormSemestre,
    Identite,
    ScolarAutorisationInscription,
    ScolarFormSemestreValidation,
    ScolarNews,
    Scolog,
    UniteEns,
)
from app.scodoc import codes_cursus
from app.scodoc import sco_cache
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import json_error


@bp.route("/formsemestre/<int:formsemestre_id>/decisions_jury")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/decisions_jury")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def decisions_jury(formsemestre_id: int):
    """Décisions du jury des étudiants du formsemestre."""
    # APC, pair:
    formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
    if formsemestre.formation.is_apc():
        app.set_sco_dept(formsemestre.departement.acronym)
        rows = jury_but_results.get_jury_but_results(formsemestre)
        return rows
    else:
        raise ScoException("non implemente")


def _news_delete_jury_etud(etud: Identite):
    "génère news sur effacement décision"
    # n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
    url = url_for(
        "scolar.ficheEtud", scodoc_dept=etud.departement.acronym, etudid=etud.id
    )
    ScolarNews.add(
        typ=ScolarNews.NEWS_JURY,
        obj=etud.id,
        text=f"""Suppression décision jury pour <a href="{url}">{etud.nomprenom}</a>""",
        url=url,
    )


@bp.route(
    "/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def validation_ue_delete(etudid: int, validation_id: int):
    "Efface cette validation"
    return _validation_ue_delete(etudid, validation_id)


@bp.route(
    "/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def validation_formsemestre_delete(etudid: int, validation_id: int):
    "Efface cette validation"
    # c'est la même chose (formations classiques)
    return _validation_ue_delete(etudid, validation_id)


def _validation_ue_delete(etudid: int, validation_id: int):
    "Efface cette validation (semestres classiques ou UEs)"
    etud = tools.get_etud(etudid)
    if etud is None:
        return "étudiant inconnu", 404
    validation = ScolarFormSemestreValidation.query.filter_by(
        id=validation_id, etudid=etudid
    ).first_or_404()
    # Vérification de la permission:
    # A le droit de supprimer cette validation: le chef de dept ou quelqu'un ayant
    # le droit de saisir des décisions de jury dans le formsemestre concerné s'il y en a un
    # (c'est le cas pour les validations de jury, mais pas pour les "antérieures" non
    # rattachées à un formsemestre)
    if not g.scodoc_dept:  # accès API
        if not current_user.has_permission(Permission.ScoEtudInscrit):
            return json_error(403, "opération non autorisée (117)")
    else:
        if validation.formsemestre:
            if (
                validation.formsemestre.dept_id != g.scodoc_dept_id
            ) or not validation.formsemestre.can_edit_jury():
                return json_error(403, "opération non autorisée (123)")
        elif not current_user.has_permission(Permission.ScoEtudInscrit):
            # Validation non rattachée à un semestre: on doit être chef
            return json_error(403, "opération non autorisée (126)")

    log(f"validation_ue_delete: etuid={etudid} {validation}")
    db.session.delete(validation)
    sco_cache.invalidate_formsemestre_etud(etud)
    db.session.commit()
    _news_delete_jury_etud(etud)
    return "ok"


@bp.route(
    "/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def autorisation_inscription_delete(etudid: int, validation_id: int):
    "Efface cette validation"
    etud = tools.get_etud(etudid)
    if etud is None:
        return "étudiant inconnu", 404
    validation = ScolarAutorisationInscription.query.filter_by(
        id=validation_id, etudid=etudid
    ).first_or_404()
    log(f"autorisation_inscription_delete: etuid={etudid} {validation}")
    db.session.delete(validation)
    sco_cache.invalidate_formsemestre_etud(etud)
    db.session.commit()
    _news_delete_jury_etud(etud)
    return "ok"


@bp.route(
    "/etudiant/<int:etudid>/jury/validation_rcue/record",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/validation_rcue/record",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_rcue_record(etudid: int):
    """Enregistre une validation de RCUE.
    Si une validation existe déjà pour ce RCUE, la remplace.
    The request content type should be "application/json":
    {
        "code" : str,
        "ue1_id" : int,
        "ue2_id" : int,
        // Optionnel:
        "formsemestre_id" : int,
        "date" : date_iso, // si non spécifié, now()
        "parcours_id" :int,
    }
    """
    etud = tools.get_etud(etudid)
    if etud is None:
        return json_error(404, "étudiant inconnu")
    data = request.get_json(force=True)  # may raise 400 Bad Request
    code = data.get("code")
    if code is None:
        return json_error(API_CLIENT_ERROR, "missing argument: code")
    if code not in codes_cursus.CODES_JURY_RCUE:
        return json_error(API_CLIENT_ERROR, "invalid code value")
    ue1_id = data.get("ue1_id")
    if ue1_id is None:
        return json_error(API_CLIENT_ERROR, "missing argument: ue1_id")
    try:
        ue1_id = int(ue1_id)
    except ValueError:
        return json_error(API_CLIENT_ERROR, "invalid value for ue1_id")
    ue2_id = data.get("ue2_id")
    if ue2_id is None:
        return json_error(API_CLIENT_ERROR, "missing argument: ue2_id")
    try:
        ue2_id = int(ue2_id)
    except ValueError:
        return json_error(API_CLIENT_ERROR, "invalid value for ue2_id")
    formsemestre_id = data.get("formsemestre_id")
    date_validation_str = data.get("date", datetime.datetime.now().isoformat())
    parcours_id = data.get("parcours_id")
    #
    query = UniteEns.query.filter_by(id=ue1_id)
    if g.scodoc_dept:
        query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
    ue1: UniteEns = query.first_or_404()
    query = UniteEns.query.filter_by(id=ue2_id)
    if g.scodoc_dept:
        query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
    ue2: UniteEns = query.first_or_404()
    if ue1.niveau_competence_id != ue2.niveau_competence_id:
        return json_error(
            API_CLIENT_ERROR, "UEs non associees au meme niveau de competence"
        )
    if formsemestre_id is not None:
        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()
        if (formsemestre.formation_id != ue1.formation_id) or (
            formsemestre.formation_id != ue2.formation_id
        ):
            return json_error(
                API_CLIENT_ERROR, "ues et semestre ne sont pas de la meme formation"
            )
    else:
        formsemestre = None
    try:
        date_validation = datetime.datetime.fromisoformat(date_validation_str)
    except ValueError:
        return json_error(API_CLIENT_ERROR, "invalid date string")
    if parcours_id is not None:
        parcours: ApcParcours = ApcParcours.query.get_or_404(parcours_id)
        if parcours.referentiel_id != ue1.niveau_competence.competence.referentiel_id:
            return json_error(API_CLIENT_ERROR, "niveau et parcours incompatibles")

    # Une validation pour ce niveau de compétence existe-elle ?
    validation = (
        ApcValidationRCUE.query.filter_by(etudid=etudid)
        .join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id)
        .filter_by(niveau_competence_id=ue2.niveau_competence_id)
        .first()
    )
    if validation:
        validation.code = code
        validation.date = date_validation
        validation.formsemestre_id = formsemestre_id
        validation.parcours_id = parcours_id
        validation.ue1_id = ue1_id
        validation.ue2_id = ue2_id
        operation = "update"
    else:
        validation = ApcValidationRCUE(
            code=code,
            date=date_validation,
            etudid=etudid,
            formsemestre_id=formsemestre_id,
            parcours_id=parcours_id,
            ue1_id=ue1_id,
            ue2_id=ue2_id,
        )
        operation = "record"
    db.session.add(validation)
    # invalider bulletins (les autres résultats ne dépendent pas des RCUEs):
    sco_cache.invalidate_formsemestre_etud(etud)
    db.session.commit()
    Scolog.logdb(
        method="validation_rcue_record",
        etudid=etudid,
        msg=f"Enregistrement {validation}",
        commit=True,
    )
    log(f"{operation} {validation}")
    return validation.to_dict()


@bp.route(
    "/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_rcue_delete(etudid: int, validation_id: int):
    "Efface cette validation"
    etud = tools.get_etud(etudid)
    if etud is None:
        return "étudiant inconnu", 404
    validation = ApcValidationRCUE.query.filter_by(
        id=validation_id, etudid=etudid
    ).first_or_404()
    log(f"validation_ue_delete: etuid={etudid} {validation}")
    db.session.delete(validation)
    sco_cache.invalidate_formsemestre_etud(etud)
    db.session.commit()
    _news_delete_jury_etud(etud)
    return "ok"


@bp.route(
    "/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
    methods=["POST"],
)
@api_web_bp.route(
    "/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
    methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_annee_but_delete(etudid: int, validation_id: int):
    "Efface cette validation"
    etud = tools.get_etud(etudid)
    if etud is None:
        return "étudiant inconnu", 404
    validation = ApcValidationAnnee.query.filter_by(
        id=validation_id, etudid=etudid
    ).first_or_404()
    log(f"validation_annee_but: etuid={etudid} {validation}")
    db.session.delete(validation)
    sco_cache.invalidate_formsemestre_etud(etud)
    db.session.commit()
    _news_delete_jury_etud(etud)
    return "ok"
