"""
    gestion.views_download, un module pour SLM
    - rendu des pages imprimables (via un format PDF)

    Copyright (C) 2023-2024 Georges Khaznadar <georgesk@debian.org>

    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 3 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, see <http://www.gnu.org/licenses/>.
"""
from django.http import FileResponse
from django.template.loader import get_template
from django.core.management import call_command
from django.views.decorators.csrf import ensure_csrf_cookie
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.db.models import Max, Min
from django.utils.translation import gettext_lazy as _

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import platform
import json
import traceback

from .decorators import i18n

if platform.system() != "Windows":
    pdfmetrics.registerFont(TTFont('Serif',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf'))
    pdfmetrics.registerFont(TTFont('SerifBd',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf'))
    pdfmetrics.registerFont(TTFont('SerifIt',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf'))
    pdfmetrics.registerFont(TTFont('SerifBI',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf'))


import trml2pdf
from outils.common import safeIdent

import io
import os
import zipfile
from datetime import date, datetime

from .models import Eleves, Classes, MIN_NO_CAUTION, Caution, Parametres, \
    Courriers, Prets, DATE111, Materiel, Eleves_de_siecle, TypeMateriel, \
    Filieres, Series, Disciplines, Niveaux, NOCLASS, LIMITE_ANNEE_SCOLAIRE

from manuels.settings import BASE_DIR, MEDIA_ROOT

@ensure_csrf_cookie
@i18n
def render_pdf(request,template, context, filename=_("téléchargement.pdf")):
    """
    Rendu sous forme d'un fichier PDF ; template est un modèle au format
    RML que définit Reportlab, context est un dictionnaire pour interpréter
    le modèle.

    Pour le standard RML, voir à https://www.reportlab.com/extra/docs/

    @param request la requête
    @param template le modèle
    @param context dictionnaire pour interpréter le modèle
    @param filename suggestion de nom de fichier pour le téléchargement ;
        "download.pdf" par défaut
    """
    tpl = get_template(template)
    try:
        rendered = tpl.render(context)
        buffer = io.BytesIO(trml2pdf.parseString(rendered))
    except Exception:
        tpl = get_template("gestion/slm_print_error.rml")
        err_lines = traceback.format_exc().split("\n")
        buffer = io.BytesIO(
            trml2pdf.parseString(
                tpl.render({
                    "err_lines": err_lines,
                    "template": template,
                })))
    buffer.seek(0)
    return FileResponse(buffer, as_attachment=True, filename=str(filename))

def manuel_bidon():
    """
    Renvoie un manuel bidon s'il n'y a encore rien dans la base de données.
    Si nécessaire, crée par effet de bord :
    - un type de matériel "Manuel scolaire"
    - une discipline "LETTRES"
    - un niveau "Seconde"
    - une filière "Générale"
    - une série "Toutes séries de la filière"
    """
    type_materiel = TypeMateriel.objects.all()
    if type_materiel:
        tm = type_materiel[0]
    else:
        tm = TypeMateriel(
            libelle_long = _("Manuel scolaire"),
            libelle_court = _("Manu"),
            Tarif_Perdu = ".",
            Tarif_Degradation = ".",
            Choix_Tarif_Degradation = False,
            Appliquer_Demi_Tarif = False,
        )
        tm.save()
    d = Disciplines.objects.all()
    if d:
        discipline = d[0]
    else:
        discipline = Disciplines(
            libelle = _("LETTRES"),
            abrege = _("LET"),
        )
        discipline.save()
    n = Niveaux.objects.all()
    if n:
        niveau = n[0]
    else:
        niveau = Niveaux(
            libelle = _("Seconde"),
            abrege = _("2NDE"),
            rang = "E",
        )
        niveau.save()
    f = Filieres.objects.all()
    if f:
        filiere = f[0]
    else:
        filiere = Filieres(
            nom = _("Générale"),
        )
        filiere.save()
    s = Series.objects.all()
    if s:
        serie = s[0]
    else:
        serie =  Series(
            libelle_court = _("toutes"),
            libelle_long = _("Toutes séries de la filière"),
        )
        serie.save()
        
    return Materiel(
        type_materiel = tm,     # instance de TypeMateriel
        titre = _("C'est un livre bidon"),
        editeur = _("Éditeur"),
        ISBN = _("ISBN"),
        discipline = discipline, # instance de Disciplines
        niveau = niveau,         # instance de Niveaux
        filiere = filiere,       # instance de Filieres
        serie = serie,           # instance de Series
    )

def catalogue_print(request):
    manuels = Materiel.objects.all().order_by("-Annee_Depot_Legal")
    if not manuels:
        manuels = [manuel_bidon()]    
    params, logo = constantes()
    return render_pdf(
        request,
        'gestion/catalogue.rml', {
            "manuels": manuels,
            # Translators : this is a format for strftime
            "date": datetime.now().strftime(str(_("%d %m %Y à %H:%M"))),
            "logo": logo,
        }, filename = _("catalogue.pdf"))

@ensure_csrf_cookie
def saveDB(request):
    """
    Enregistrement de la base de données : un fichier d'export au format
    JSON ou SQLITE zippé est renvoyé, selon le paramètre format
    """
    # on vide la table temporaire  qui correspond à Eleves_de_siecle
    # avant tout enregistrement
    Eleves_de_siecle.objects.all().delete()
    format = request.GET.get("format", "")
    buffer = io.BytesIO()
    zipf = zipfile.ZipFile(buffer, "w")
    if format == "json":
        dump = io.StringIO()
        call_command('dumpdata',format='json',indent=2,stdout=dump)
        dump.seek(0)
        zipf.writestr("db.json", dump.read(),compress_type=zipfile.ZIP_DEFLATED)
        zipf.close()
        filename = "db.json.zip"
    else:
        assert(format == "sqlite")
        dbpath = os.path.join(BASE_DIR, "db.sqlite3")
        zipf.writestr("db.sqlite3", open(dbpath, "rb").read(),
                      compress_type=zipfile.ZIP_DEFLATED)
        zipf.close()
        filename = "db.sqlite-{date}.zip".format(date = datetime.now().strftime(
            "%Y-%m-%d_%H-%M-%S"))
    buffer.seek(0)
    return FileResponse(buffer, as_attachment=True, filename=filename)

def prets_mkPDF(request):
    """
    Crée la fiche de prêt pour un élève. Utilise la paramètre posté "nomprenom"
    """
    nomprenom = request.POST.get("nomprenom")
    eleve =  Eleves.objects.get(NomPrenomEleve = nomprenom)
    # sélectionne les prêts à l'élève qui n'ont pas encore été rendus
    manuels = Prets.objects.filter(
        eleve = eleve
    ).filter(date_retour = DATE111,
             Date_Declaration_Perte = DATE111,
             inventaire__paye__isnull = True).order_by(
        "inventaire__materiel__discipline__abrege",
        "inventaire__id"
    )
    lesprix = [m.inventaire.materiel.Prix for m in manuels]
    messages = Courriers.objects.order_by("N").reverse()
    messages_pret = messages[0].Pret_Recommandation
    total = sum((float(p.replace(",",".")) for p in lesprix if p))
    params, logo = constantes()
    return render_pdf(
        request,
        'gestion/fiche_pret.rml', {
            "eleve": eleve,
            "manuels": manuels,
            "messages": messages_pret.split("\n"),
            "date": datetime.now(),
            "logo": logo,
            "sum": total,
            "params": params,
        },
        filename=_("pret_{}.pdf").format(safeIdent(nomprenom))
    )

def avenant_mkPDF(request):
    """
    Crée un avenant à la fiche de prêt pour un élève.
    Utilise les paramètres postés "nomprenom" et "n" (n vaut "1" ou "2")
    """
    nomprenom = request.POST.get("nomprenom")
    n = request.POST.get("n")
    eleve =  Eleves.objects.get(NomPrenomEleve = nomprenom)
    # sélectionne les prêts à l'élève qui n'ont pas encore été rendus
    manuels = Prets.objects.filter(
        eleve = eleve
    ).filter((Q(date_retour = DATE111) & Q(Date_Declaration_Perte = DATE111)) \
             | Q(date_retour = datetime.now().date())).order_by(
        "inventaire__materiel__discipline__abrege",
        "inventaire__id"
    )
    count_total = len(manuels)
    min_max_date = manuels.aggregate(
            m=Min('date_pret'), M=Max('date_pret'), R=Max('date_retour'))
    date_avenant = max(min_max_date["M"], min_max_date["R"]) if \
        min_max_date["M"] else None
    # les livres qui viennent juste d'être rendus ?
    juste_rendus = Prets.objects.filter(
        eleve = eleve
    ).filter(date_retour = date_avenant).order_by(
        "inventaire__materiel__discipline__abrege",
        "inventaire__id"
    )
    # les manuels qui viennent juste d'être prêtés ?
    manuels = manuels.filter(date_pret = date_avenant)
    lesprix = [m.inventaire.materiel.Prix for m in manuels]
    messages = Courriers.objects.order_by("N").reverse()
    messages_pret = messages[0].Pret_Recommandation
    total = sum((float(p.replace(",",".")) for p in lesprix if p))
    params, logo = constantes()
    return render_pdf(
        request,
        f'gestion/avenant{n}_pret.rml', {
            "eleve": eleve,
            "manuels": manuels,
            "juste_rendus": juste_rendus,
            "count_total": count_total,
            "messages": messages_pret.split("\n"),
            "date": datetime.now(),
            "logo": logo,
            "sum": total,
            "params": params,
        },
        filename=_("avenant{}_{}.pdf").format(n,safeIdent(nomprenom))
    )

def imprime_restitution(request):
    """
    imprime une fiche de restitution,
    étant donné les paramètres postés "nomprenom" et "liste_manuels"
    """
    nomprenom = request.POST.get("nomprenom")
    liste_manuels = json.loads(request.POST.get("liste_manuels"))
    eleve =  Eleves.objects.get(NomPrenomEleve = nomprenom)
    # sélectionne les prêts correspondant à liste_manuels
    manuels = Prets.objects.filter(
        inventaire__id__in = liste_manuels, eleve = eleve,
    ).order_by(
        "inventaire__materiel__discipline__abrege",
        "inventaire__id"
    )
    lesprix = [m.inventaire.materiel.Prix for m in manuels]
    messages = Courriers.objects.order_by("N").reverse()
    messages_rendu = messages[0].Retour_Recommandation
    params, logo = constantes()
    tarifs = [m.tarif for m in manuels]
    rendus = [m for m in manuels if m.isRendu]
    reste_du = 0
    for m in manuels:
        if not m.isRendu:
            reste_du += m.tarif
    return render_pdf(
        request,
        'gestion/fiche_rendu.rml', {
            "eleve": eleve,
            "manuels": manuels,
            "messages": messages_rendu.split("\n"),
            "date": datetime.now(),
            "logo": logo,
            "params": params,
            "total": sum(tarifs),
            "nb_non_rendu": len(manuels) - len(rendus),
            "lesprix": lesprix,
            "reste_du": reste_du,
        },
        filename=_("rendu_{}.pdf").format(safeIdent(nomprenom))
    )

def imprime_memoire(request):
    """
    imprime une fiche de mémoire pour pertes
    étant donné les paramètres postés "nomprenom" et "liste_manuels"
    """
    nomprenom = request.POST.get("nomprenom")
    liste_manuels = json.loads(request.POST.get("liste_manuels"))
    eleve =  Eleves.objects.get(NomPrenomEleve = nomprenom)
    # sélectionne les prêts à l'élève qui n'ont pas encore été rendus
    manuels = Prets.objects.filter(
        inventaire__id__in = liste_manuels
    ).filter(Q(Date_Declaration_Perte__gt = "0001-01-01")).order_by(
        "inventaire__materiel__discipline__abrege",
        "inventaire__id"
    )
    lesprix = [m.inventaire.materiel.Prix for m in manuels]
    messages = Courriers.objects.order_by("N").reverse()
    messages_rendu = messages[0].Memoire
    params, logo = constantes()
    tarifs = [m.tarif for m in manuels]
    rendus = [m for m in manuels if m.isRendu]
    return render_pdf(
        request,
        'gestion/fiche_memoire.rml', {
            "eleve": eleve,
            "manuels": manuels,
            "messages": [mark_safe(l) for l in messages_rendu.split("\n")],
            "date": datetime.now(),
            "logo": logo,
            "params": params,
            "total": sum(tarifs),
            "nb_non_rendu": len(manuels) - len(rendus),
            "lesprix": lesprix,
        },
        filename=_("memoire_{}.pdf").format(safeIdent(nomprenom))
    )

def constantes():
    """
    Récupère les "constantes" de l'établissement à la date courante
    Attention, il faut qu'il y ait un enregistrement dans la table
    Parametres, valide pour l'année scolaire en cours !
    @return un doublet params, logo qui sont une instance de
      Parametres et le chemin absolu vers l'image qu'on y trouve
    """
    annee = int(date.today().strftime("%Y"))
    params = Parametres.objects.filter(AnneeScolaire__contains=str(annee)) \
        or Parametres.objects.filter(AnneeScolaire__contains=str(annee-1))
    if params:
        params = params[0]
    else:
        # on crée des constantes bidon
        params = Parametres(
            NomAcademie = _("Test"),
            NomEts = _("Lycée bidon"),
            CodeRNE = _("0590071g"),
            Commune = _("Nulle part"),
            Titre_ChefEts = _("Proviseur"),
            Civilite_ChefEts = _("Mme"),
            NomChefEts = _("La proviseure"),
            Civilite_AgentComptable = _("M"),
            NomAgentComptable = _("L'intendant"),
            AnneeScolaire = datetime.now().strftime("%Y"),
            Adresse = _("1 rue du lycée"),
            CodePostal = "00000",
            Telephone = "010203040506",
        )
    logo = os.path.join(MEDIA_ROOT, str(params.Logo))
    if not os.path.isfile(logo):
        logo = os.path.join(MEDIA_ROOT, "default", "nologo.png")
    return params, logo

@ensure_csrf_cookie
def cahier_cautions(request):
    """
    Présente le cahier des cautions sous une forme imprimable
    Chaque ligne contient les champs d'une instance d'élève :
    numéro de caution, Nom_de_famille, Prenom, date_inscription_coop
    et laisse de la place pour ajouter des commentaires
    """
    # on ne sélectionne que les élèves dont le numéro de caution
    # a été créé un jour par SLM, pas ceux qui ont été hérités de
    # Gemasco.
    cautions = Caution.objects.filter( numero__gt = MIN_NO_CAUTION )
    if not cautions:
        # on crée une liste avec une seule instance bidon de Caution
        bidon = Caution(
            numero = MIN_NO_CAUTION,
            nom = _("Il n'y a pas encore d'élèves"),
            prenom = _("Il faudrait nourrir la base de données !"),
            date = datetime.now(),
            # Translators : this is a name of a French class at the
            # begin of the high school (students are aged 15)
            classe = _("2d00"),
            commentaire=_("Il faudrait entrer quelques élèves dans la base !"),
        )
        cautions = [bidon]
    premiere_date = min((c.date for c in cautions))
    derniere_date = max((c.date for c in cautions))
    if premiere_date.month >= 7:
        year = premiere_date.year
    else:
        year = premiere_date.year - 1
    debut_cautions = date(year, 7, 1)
    if derniere_date.month < 7:
        year = derniere_date.year
    else:
        year = derniere_date.year + 1
    fin_cautions = date(year, 7, 1)
    cautions_par_annee = {}
    annee_cautions = debut_cautions
    year = annee_cautions.year                   # ex: 2023
    while year < fin_cautions.year:
        s_year = f"{year}/{int(str(year)[2:]) + 1}"  # ex: "2023/24"
        next_year = year + 1
        debut = date(year, 7, 1)
        fin = date(next_year, 7, 1)
        cautions_par_annee[year] = {
            "school_year": s_year,
            "cautions": Caution.objects.filter(
                numero__gt = MIN_NO_CAUTION, date__gte = debut, date__lt = fin)
        }
        year = next_year
    return render_pdf(
        request,
        'gestion/cahier_cautions.rml', {
            "cautions_par_annee": cautions_par_annee,
            "NOCLASS": NOCLASS,
            "colWidths": "1.8cm,5.6cm,3.4cm,2.5cm,4.7cm",
            "vpadding": "0.964mm", # ajustement pour des lignes de 8 mm
        },
        filename=_("cahier_cautions.pdf"),
    )

@ensure_csrf_cookie
def listes_eleves(request):
    """
    Présente les listes d'élèves sous une forme imprimable
    Chaque ligne contient concerne un élève :
    numéro de caution, case à cocher (vide initialement) s'il prend des livres,
    Nom_de_famille, Prenom et laisse de la place pour ajouter des commentaires
    """
    classes = [c for c in Classes.classes_importantes() \
               if c.libelle != NOCLASS]
    listes = {
        c: Eleves.objects.filter(classe = c).order_by('NomPrenomEleve') \
        for c in classes
    }
    if not listes:
        # on renvoie un dictionnaire avec un élève bidon
        bidon = Eleves(
            Nom_de_famille = _("Il n'y a pas encore d'élèves"),
            Prenom = _("Il faudrait nourrir la base de données !"),
        )
        listes = {
            _("2d00"): [bidon]
        }
    return render_pdf(
        request,
        'gestion/listes_eleves.rml', {
            "date": datetime.now().strftime("%d/%m/%Y"),
            "listes": listes,
            "min_livres_emoji": 3, # case cochée pour plus de trois livres
            "colWidths": "1.8cm,1cm,5.6cm,3.4cm,6.2cm",
        },
        filename=_("listes_eleves.pdf"),
    )
    
def messages_eleves(request):
    """
    Produit un document avec tous les messages non vides destinés aux
    élèves
    """
    eleves = Eleves.objects.exclude(message="").order_by(
        'Lib_Structure','NomPrenomEleve')
    for e in eleves:
        e.message = "</para><para>".join(e.message.split("\n"))
    return render_pdf(
        request,
        'gestion/messages_eleves.rml', {
            "date": datetime.now().strftime("%d %b %Y"),
            "eleves": eleves,
            "colWidths": "6cm,12cm",
        },
        filename=_("listes_eleves.pdf"),
    )
    
def pqcl(request, ident):
    """
    Fonction de rappel pour la routine Javascript pour_qui_ce_livre
    Produit un document avec tous les élèves devant recevoir un livre
    @param  ident id pour le livre
    """
    manuel = Materiel.objects.get(id=ident)
    eleves = manuel.pour_qui_ce_livre()
    eleves.sort(key=lambda e: e.NomPrenomEleve)
    eleves.sort(key=lambda e: e.Lib_Structure)
    return render_pdf(
        request,
        'gestion/pour_qui_ce_livre.rml', {
            "date": datetime.now().strftime("%d %b %Y"),
            "livre": manuel,
            "eleves": eleves,
        },
        filename=_("pour_qui_ce_livre.pdf"),
    )
