# -*- coding: utf-8 -*-

##############################################################################
#
# ScoDoc
#
# 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@viennet.net
#
##############################################################################

"""
Vues "modernes" des formsemestres
Emmanuel Viennet, 2023
"""

import datetime
import io

from flask import flash, redirect, render_template, url_for
from flask import current_app, g, request
import PIL

from app import db, log
from app.decorators import (
    scodoc,
    permission_required,
)
from app.formations import formation_io, formation_versions
from app.forms.formsemestre import (
    change_formation,
    edit_modimpls_codes_apo,
    edit_description,
)
from app.models import (
    Formation,
    FormSemestre,
    FormSemestreDescription,
    FORMSEMESTRE_DISPOSITIFS,
    ScoDocSiteConfig,
)
from app.scodoc import (
    sco_edt_cal,
    sco_groups_view,
)
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
from app.views import notes_bp as bp
from app.views import ScoData


@bp.route(
    "/formsemestre_change_formation/<int:formsemestre_id>", methods=["GET", "POST"]
)
@scodoc
@permission_required(Permission.EditFormSemestre)
def formsemestre_change_formation(formsemestre_id: int):
    """Propose de changer un formsemestre de formation.
    Cette opération est bien sûr impossible... sauf si les deux formations sont identiques.
    Par exemple, on vient de créer une formation, et on a oublié d'y associé un formsemestre
    existant.
    """
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    formation_dict = formation_io.formation_export_dict(
        formsemestre.formation, export_external_ues=True, ue_reference_style="acronyme"
    )
    formations = [
        formation
        for formation in Formation.query.filter_by(
            dept_id=formsemestre.dept_id, acronyme=formsemestre.formation.acronyme
        )
        if formation.id != formsemestre.formation.id
        and formation_versions.formations_are_equals(
            formation, formation2_dict=formation_dict
        )
    ]
    form = change_formation.gen_formsemestre_change_formation_form(formations)
    if request.method == "POST" and form.validate:
        if not form.cancel.data:
            new_formation_id = form.radio_but.data
            if new_formation_id is None:  # pas de choix radio
                flash("Pas de formation sélectionnée !")
                return render_template(
                    "formsemestre/change_formation.j2",
                    form=form,
                    formations=formations,
                    formsemestre=formsemestre,
                    sco=ScoData(formsemestre=formsemestre),
                )
            else:
                new_formation: Formation = Formation.query.filter_by(
                    dept_id=g.scodoc_dept_id, formation_id=new_formation_id
                ).first_or_404()
                formation_versions.formsemestre_change_formation(
                    formsemestre, new_formation
                )
                flash("Formation du semestre modifiée")
        return redirect(
            url_for(
                "notes.formsemestre_status",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
            )
        )
    # GET
    return render_template(
        "formsemestre/change_formation.j2",
        form=form,
        formations=formations,
        formsemestre=formsemestre,
        sco=ScoData(formsemestre=formsemestre),
    )


@bp.route(
    "/formsemestre_edit_modimpls_codes/<int:formsemestre_id>", methods=["GET", "POST"]
)
@scodoc
@permission_required(Permission.EditFormSemestre)
def formsemestre_edit_modimpls_codes(formsemestre_id: int):
    """Edition des codes Apogée et EDT"""
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    form = edit_modimpls_codes_apo.EditModimplsCodesForm(formsemestre)

    if request.method == "POST" and form.validate:
        if not form.cancel.data:
            # record codes
            for modimpl in formsemestre.modimpls_sorted:
                field_apo = getattr(form, f"modimpl_apo_{modimpl.id}")
                field_edt = getattr(form, f"modimpl_edt_{modimpl.id}")
                if field_apo and field_edt:
                    modimpl.code_apogee = field_apo.data.strip() or None
                    modimpl.edt_id = field_edt.data.strip() or None
                    log(f"setting codes for {modimpl}: apo={field_apo} edt={field_edt}")
                    db.session.add(modimpl)
            db.session.commit()
            flash("Codes enregistrés")
        return redirect(
            url_for(
                "notes.formsemestre_status",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
            )
        )
    # GET
    for modimpl in formsemestre.modimpls_sorted:
        field_apo = getattr(form, f"modimpl_apo_{modimpl.id}")
        field_edt = getattr(form, f"modimpl_edt_{modimpl.id}")
        field_apo.data = modimpl.code_apogee or ""
        field_edt.data = modimpl.edt_id or ""
    return render_template(
        "formsemestre/edit_modimpls_codes.j2",
        form=form,
        formsemestre=formsemestre,
        sco=ScoData(formsemestre=formsemestre),
    )


@bp.route("/formsemestre/edt/<int:formsemestre_id>")
@scodoc
@permission_required(Permission.ScoView)
def formsemestre_edt(formsemestre_id: int):
    """Expérimental: affiche emploi du temps du semestre"""

    current_date = request.args.get("current_date")
    show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
    view = request.args.get("view", "week")
    views_names = {"day": "Jour", "month": "Mois", "week": "Semaine"}
    if view not in views_names:
        raise ScoValueError("valeur invalide pour le paramètre view")
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    cfg = ScoDocSiteConfig.query.filter_by(name="assi_morning_time").first()
    hour_start = cfg.value.split(":")[0].lstrip(" 0") if cfg else "7"
    cfg = ScoDocSiteConfig.query.filter_by(name="assi_afternoon_time").first()
    hour_end = cfg.value.split(":")[0].lstrip(" 0") if cfg else "18"
    group_ids = request.args.getlist("group_ids", int)
    groups_infos = sco_groups_view.DisplayedGroupsInfos(
        group_ids=group_ids,
        formsemestre_id=formsemestre_id,
        empty_list_select_all=False,
    )
    return render_template(
        "formsemestre/edt.j2",
        current_date=current_date,
        formsemestre=formsemestre,
        hour_start=hour_start,
        hour_end=hour_end,
        form_groups_choice=sco_groups_view.form_groups_choice(
            groups_infos,
            submit_on_change=True,
            default_deselect_others=False,
            with_deselect_butt=True,
        ),
        groups_query_args=groups_infos.groups_query_args,
        sco=ScoData(formsemestre=formsemestre),
        show_modules_titles=show_modules_titles,
        title=f"EDT S{formsemestre.semestre_id} {formsemestre.titre_formation()}",
        view=view,
        views_names=views_names,
    )


@bp.route("/formsemestre/edt_help_config/<int:formsemestre_id>")
@scodoc
@permission_required(Permission.ScoView)
def formsemestre_edt_help_config(formsemestre_id: int):
    """Page d'aide à la configuration de l'extraction emplois du temps
    Affiche les identifiants extraits de l'ics et ceux de ScoDoc.
    """
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    edt2group = sco_edt_cal.formsemestre_retreive_groups_from_edt_id(formsemestre)
    events_sco, edt_groups_ids = sco_edt_cal.load_and_convert_ics(formsemestre)
    return render_template(
        "formsemestre/edt_help_config.j2",
        formsemestre=formsemestre,
        edt2group=edt2group,
        edt_groups_ids=edt_groups_ids,
        events_sco=events_sco,
        sco=ScoData(formsemestre=formsemestre),
        ScoDocSiteConfig=ScoDocSiteConfig,
        title="Aide configuration EDT",
    )


@bp.route(
    "/formsemestre_description/<int:formsemestre_id>/edit", methods=["GET", "POST"]
)
@scodoc
@permission_required(Permission.EditFormSemestre)
def edit_formsemestre_description(formsemestre_id: int):
    "Edition de la description d'un formsemestre"
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    if not formsemestre.description:
        formsemestre.description = FormSemestreDescription()
        db.session.add(formsemestre)
        db.session.commit()
    formsemestre_description = formsemestre.description
    form = edit_description.FormSemestreDescriptionForm(obj=formsemestre_description)
    ok = True
    if form.validate_on_submit():
        if form.cancel.data:  # cancel button
            return redirect(
                url_for(
                    "notes.formsemestre_editwithmodules",
                    scodoc_dept=g.scodoc_dept,
                    formsemestre_id=formsemestre.id,
                )
            )
        # Vérification valeur dispositif
        if form.dispositif.data not in FORMSEMESTRE_DISPOSITIFS:
            flash("Dispositif inconnu", "danger")
            ok = False

        # Vérification dates inscriptions
        if form.date_debut_inscriptions.data:
            try:
                date_debut_inscriptions_dt = datetime.datetime.strptime(
                    form.date_debut_inscriptions.data, scu.DATE_FMT
                )
            except ValueError:
                flash("Date de début des inscriptions invalide", "danger")
                form.set_error("date début invalide", form.date_debut_inscriptions)
                ok = False
        else:
            date_debut_inscriptions_dt = None
        if form.date_fin_inscriptions.data:
            try:
                date_fin_inscriptions_dt = datetime.datetime.strptime(
                    form.date_fin_inscriptions.data, scu.DATE_FMT
                )
            except ValueError:
                flash("Date de fin des inscriptions invalide", "danger")
                form.set_error("date fin invalide", form.date_fin_inscriptions)
                ok = False
        else:
            date_fin_inscriptions_dt = None
        if ok:
            # dates converties
            form.date_debut_inscriptions.data = date_debut_inscriptions_dt
            form.date_fin_inscriptions.data = date_fin_inscriptions_dt
            # Affecte tous les champs sauf les images:
            form_image = form.image
            del form.image
            form_photo_ens = form.photo_ens
            del form.photo_ens
            form.populate_obj(formsemestre_description)
            # Affecte les images:
            for field, form_field in (
                ("image", form_image),
                ("photo_ens", form_photo_ens),
            ):
                if form_field.data:
                    image_data = form_field.data.read()
                    max_length = current_app.config.get("MAX_CONTENT_LENGTH")
                    if max_length and len(image_data) > max_length:
                        flash(
                            f"Image trop grande ({field}), max {max_length} octets",
                            "danger",
                        )
                        return redirect(
                            url_for(
                                "notes.edit_formsemestre_description",
                                formsemestre_id=formsemestre.id,
                                scodoc_dept=g.scodoc_dept,
                            )
                        )
                    try:
                        _ = PIL.Image.open(io.BytesIO(image_data))
                    except PIL.UnidentifiedImageError:
                        flash(
                            f"Image invalide ({field}), doit être une image",
                            "danger",
                        )
                        return redirect(
                            url_for(
                                "notes.edit_formsemestre_description",
                                formsemestre_id=formsemestre.id,
                                scodoc_dept=g.scodoc_dept,
                            )
                        )
                    setattr(formsemestre_description, field, image_data)

            db.session.commit()
            flash("Description enregistrée", "success")
            return redirect(
                url_for(
                    "notes.formsemestre_status",
                    formsemestre_id=formsemestre.id,
                    scodoc_dept=g.scodoc_dept,
                )
            )

    return render_template(
        "formsemestre/edit_description.j2",
        form=form,
        formsemestre=formsemestre,
        formsemestre_description=formsemestre_description,
        sco=ScoData(formsemestre=formsemestre),
        title="Modif. description semestre",
    )