from django.shortcuts import render,get_object_or_404,get_list_or_404
from .models import RegistrationForm, Student, Guardian, Sibling
import time
from datetime import date, datetime, timedelta
from zoneinfo import ZoneInfo
from django.conf import settings
from N3wtSchool import renderers
from N3wtSchool import bdd

from io import BytesIO
import base64
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.utils import ImageReader
from reportlab.graphics import renderPDF
from django.core.files.base import ContentFile
from django.core.files import File
from pathlib import Path
import os
from enum import Enum
from urllib.parse import unquote_to_bytes

import random
import string
from rest_framework.parsers import JSONParser
from PyPDF2 import PdfMerger, PdfReader, PdfWriter
from PyPDF2.errors import PdfReadError

import shutil
import logging

import json
from django.http import QueryDict
from rest_framework.response import Response
from rest_framework import status
from svglib.svglib import svg2rlg

logger = logging.getLogger(__name__)


def _draw_signature_data_url(cnv, data_url, x, y, width, height):
    """
    Dessine une signature issue d'un data URL dans un canvas ReportLab.
    Supporte les images raster (PNG/JPEG/...) et SVG.
    Retourne True si la signature a pu etre dessinee.
    """
    if not isinstance(data_url, str) or not data_url.startswith("data:image"):
        return False

    try:
        header, payload = data_url.split(',', 1)
    except ValueError:
        return False

    is_base64 = ';base64' in header
    mime_type = header.split(':', 1)[1].split(';', 1)[0] if ':' in header else ''

    try:
        raw_bytes = base64.b64decode(payload) if is_base64 else unquote_to_bytes(payload)
    except Exception as e:
        logger.error(f"[_draw_signature_data_url] Decodage impossible: {e}")
        return False

    # Support SVG via svglib (deja present dans requirements)
    if mime_type == 'image/svg+xml':
        try:
            drawing = svg2rlg(BytesIO(raw_bytes))
            if drawing is None:
                return False

            src_w = float(getattr(drawing, 'width', 0) or 0)
            src_h = float(getattr(drawing, 'height', 0) or 0)
            if src_w <= 0 or src_h <= 0:
                return False

            scale = min(width / src_w, height / src_h)
            draw_w = src_w * scale
            draw_h = src_h * scale
            offset_x = x + (width - draw_w) / 2
            offset_y = y + (height - draw_h) / 2

            cnv.saveState()
            cnv.translate(offset_x, offset_y)
            cnv.scale(scale, scale)
            renderPDF.draw(drawing, cnv, 0, 0)
            cnv.restoreState()
            return True
        except Exception as e:
            logger.error(f"[_draw_signature_data_url] Rendu SVG impossible: {e}")
            return False

    # Support images raster classiques
    try:
        img_reader = ImageReader(BytesIO(raw_bytes))
        cnv.drawImage(
            img_reader,
            x,
            y,
            width=width,
            height=height,
            preserveAspectRatio=True,
            mask='auto',
        )
        return True
    except Exception as e:
        logger.error(f"[_draw_signature_data_url] Rendu raster impossible: {e}")
        return False

def save_file_replacing_existing(file_field, filename, content, save=True):
    """
    Sauvegarde un fichier en écrasant l'existant s'il porte le même nom.
    Évite les suffixes automatiques Django (ex: fichier_N5QdZpk.pdf).

    Args:
        file_field: Le FileField Django (ex: registerForm.registration_file)
        filename: Le nom du fichier à sauvegarder
        content: Le contenu du fichier (File, BytesIO, ContentFile, etc.)
        save: Si True, sauvegarde l'instance parente
    """
    # Supprimer l'ancien fichier s'il existe
    if file_field and file_field.name:
        try:
            if hasattr(file_field, 'path') and os.path.exists(file_field.path):
                os.remove(file_field.path)
                logger.debug(f"[save_file] Ancien fichier supprimé: {file_field.path}")
        except Exception as e:
            logger.error(f"[save_file] Erreur suppression ancien fichier: {e}")

    # Sauvegarder le nouveau fichier
    file_field.save(filename, content, save=save)

def save_file_field_without_suffix(instance, field_name, filename, content, save=False):
    """
    Sauvegarde un fichier dans un FileField Django en ecrasant le precedent,
    sans laisser Django generer de suffixe (_abc123).

    Args:
        instance: instance Django portant le FileField
        field_name: nom du FileField (ex: 'file')
        filename: nom de fichier cible (basename)
        content: contenu fichier (ContentFile, File, etc.)
        save: si True, persiste immediatement l'instance
    """
    file_field = getattr(instance, field_name)
    field = instance._meta.get_field(field_name)
    storage = file_field.storage

    target_name = field.generate_filename(instance, filename)

    # Supprimer le fichier actuellement reference si different
    if file_field and file_field.name and file_field.name != target_name:
        try:
            if storage.exists(file_field.name):
                storage.delete(file_field.name)
        except Exception as e:
            logger.error(f"[save_file_field_without_suffix] Erreur suppression ancien fichier ({file_field.name}): {e}")

    # Supprimer explicitement la cible si elle existe deja
    try:
        if storage.exists(target_name):
            storage.delete(target_name)
    except Exception as e:
        logger.error(f"[save_file_field_without_suffix] Erreur suppression cible ({target_name}): {e}")

    # Sauvegarde: la cible n'existe plus, donc pas de suffixe
    file_field.save(filename, content, save=save)

def build_payload_from_request(request):
    """
    Normalise la request en payload prêt à être donné au serializer.
    - supporte multipart/form-data où le front envoie 'data' (JSON string) ou un fichier JSON + fichiers
    - supporte application/json ou form-data simple
    Retour: (payload_dict, None) ou (None, Response erreur)
    """
    # Si c'est du JSON pur (Content-Type: application/json)
    if hasattr(request, 'content_type') and 'application/json' in request.content_type:
        try:
            # request.data contient déjà le JSON parsé par Django REST
            payload = dict(request.data) if hasattr(request.data, 'items') else request.data
            logger.info(f"JSON payload extracted: {payload}")
            return payload, None
        except Exception as e:
            logger.error(f'Error processing JSON: {e}')
            return None, Response({'error': "Invalid JSON", 'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)

    # Cas multipart/form-data avec champ 'data'
    data_field = request.data.get('data') if hasattr(request.data, 'get') else None
    if data_field:
        try:
            # Si 'data' est un fichier (InMemoryUploadedFile ou fichier similaire), lire et décoder
            if hasattr(data_field, 'read'):
                raw = data_field.read()
                if isinstance(raw, (bytes, bytearray)):
                    text = raw.decode('utf-8')
                else:
                    text = raw
                payload = json.loads(text)
            # Si 'data' est bytes déjà
            elif isinstance(data_field, (bytes, bytearray)):
                payload = json.loads(data_field.decode('utf-8'))
            # Si 'data' est une string JSON
            elif isinstance(data_field, str):
                payload = json.loads(data_field)
            else:
                # type inattendu
                raise ValueError(f"Unsupported 'data' type: {type(data_field)}")
        except (json.JSONDecodeError, ValueError, UnicodeDecodeError) as e:
            logger.error(f'Invalid JSON in "data": {e}')
            return None, Response({'error': "Invalid JSON in 'data'", 'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
    else:
        payload = request.data.copy() if hasattr(request.data, 'copy') else dict(request.data)
        if isinstance(payload, QueryDict):
            payload = payload.dict()

    # Attacher les fichiers présents (ex: photo, files.*, etc.), sauf 'data' (déjà traité)
    for f_key, f_val in request.FILES.items():
        if f_key == 'data':
            # remettre le pointeur au début si besoin (déjà lu) — non indispensable ici mais sûr
            try:
                f_val.seek(0)
            except Exception:
                pass
            # ne pas mettre le fichier 'data' dans le payload (c'est le JSON)
            continue
        payload[f_key] = f_val

    return payload, None

def create_templates_for_registration_form(register_form):
    """
    Idempotent:
    - supprime les templates existants qui ne correspondent pas
      aux masters du fileGroup courant du register_form (et supprime leurs fichiers).
    - crée les templates manquants pour les masters du fileGroup courant.
    Retourne la liste des templates créés.
    """
    from Subscriptions.models import (
        RegistrationSchoolFileMaster,
        RegistrationSchoolFileTemplate,
        RegistrationParentFileMaster,
        RegistrationParentFileTemplate,
        registration_school_file_upload_to,
    )

    created = []
    logger.info("util.create_templates_for_registration_form - create_templates_for_registration_form")

    # Récupérer les masters du fileGroup courant
    current_group = getattr(register_form, "fileGroup", None)
    if not current_group:
        # Si plus de fileGroup, supprimer tous les templates existants pour ce RF
        school_existing = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form)
        for t in school_existing:
            try:
                if getattr(t, "file", None):
                    logger.info("Deleted school template %s for RF %s", t.pk, register_form.pk)
                    t.file.delete(save=False)
            except Exception:
                logger.exception("Erreur suppression fichier school template %s", getattr(t, "pk", None))
            t.delete()
        parent_existing = RegistrationParentFileTemplate.objects.filter(registration_form=register_form)
        for t in parent_existing:
            try:
                if getattr(t, "file", None):
                    t.file.delete(save=False)
            except Exception:
                logger.exception("Erreur suppression fichier parent template %s", getattr(t, "pk", None))
            t.delete()
        return created

    school_masters = RegistrationSchoolFileMaster.objects.filter(groups=current_group).distinct()
    parent_masters = RegistrationParentFileMaster.objects.filter(groups=current_group).distinct()

    school_master_ids = {m.pk for m in school_masters}
    parent_master_ids = {m.pk for m in parent_masters}

    # Supprimer les school templates obsolètes
    for tmpl in RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form):
        if not tmpl.master_id or tmpl.master_id not in school_master_ids:
            try:
                if getattr(tmpl, "file", None):
                    tmpl.file.delete(save=False)
            except Exception:
                logger.exception("Erreur suppression fichier school template obsolète %s", getattr(tmpl, "pk", None))
            tmpl.delete()
            logger.info("Deleted obsolete school template %s for RF %s", getattr(tmpl, "pk", None), register_form.pk)

    # Supprimer les parent templates obsolètes
    for tmpl in RegistrationParentFileTemplate.objects.filter(registration_form=register_form):
        if not tmpl.master_id or tmpl.master_id not in parent_master_ids:
            try:
                if getattr(tmpl, "file", None):
                    tmpl.file.delete(save=False)
            except Exception:
                logger.exception("Erreur suppression fichier parent template obsolète %s", getattr(tmpl, "pk", None))
            tmpl.delete()
            logger.info("Deleted obsolete parent template %s for RF %s", getattr(tmpl, "pk", None), register_form.pk)

    # Créer les school templates manquants ou mettre à jour les existants si le master a changé
    for m in school_masters:
        tmpl_qs = RegistrationSchoolFileTemplate.objects.filter(master=m, registration_form=register_form)
        tmpl = tmpl_qs.first() if tmpl_qs.exists() else None

        base_slug = (m.name or "master").strip().replace(" ", "_")[:40]
        slug = f"{base_slug}_{register_form.pk}_{m.pk}"

        is_dynamic_master = (
            isinstance(m.formMasterData, dict)
            and bool(m.formMasterData.get("fields"))
        )

        # Formulaire dynamique: toujours générer le PDF final depuis le JSON
        # (aperçu admin) au lieu de copier le fichier source brut (PNG/PDF).
        if is_dynamic_master:
            base_pdf_content = None
            base_file_ext = None
            if m.file and hasattr(m.file, 'name') and m.file.name:
                base_file_ext = os.path.splitext(m.file.name)[1].lower()
                try:
                    m.file.open('rb')
                    base_pdf_content = m.file.read()
                    m.file.close()
                except Exception as e:
                    logger.error(f"Erreur lecture fichier source master dynamique: {e}")

            try:
                generated_pdf = generate_form_json_pdf(
                    register_form,
                    m.formMasterData,
                    base_pdf_content=base_pdf_content,
                    base_file_ext=base_file_ext,
                )
            except Exception as e:
                logger.error(f"Erreur génération PDF dynamique pour template: {e}")
                generated_pdf = None

            if tmpl:
                try:
                    if tmpl.file and tmpl.file.name:
                        tmpl.file.delete(save=False)
                except Exception:
                    logger.exception("Erreur suppression ancien fichier template dynamique %s", getattr(tmpl, "pk", None))

                tmpl.name = m.name or ""
                tmpl.slug = slug
                tmpl.formTemplateData = m.formMasterData or []
                if generated_pdf is not None:
                    output_filename = os.path.basename(generated_pdf.name)
                    save_file_field_without_suffix(
                        tmpl,
                        'file',
                        output_filename,
                        generated_pdf,
                        save=False,
                    )
                tmpl.save()
                created.append(tmpl)
                logger.info(
                    "util.create_templates_for_registration_form - Regenerated dynamic school template %s from master %s for RF %s",
                    tmpl.pk,
                    m.pk,
                    register_form.pk,
                )
                continue

            tmpl = RegistrationSchoolFileTemplate(
                master=m,
                registration_form=register_form,
                name=m.name or "",
                formTemplateData=m.formMasterData or [],
                slug=slug,
            )
            if generated_pdf is not None:
                output_filename = os.path.basename(generated_pdf.name)
                save_file_field_without_suffix(
                    tmpl,
                    'file',
                    output_filename,
                    generated_pdf,
                    save=False,
                )
            tmpl.save()
            created.append(tmpl)
            logger.info(
                "util.create_templates_for_registration_form - Created dynamic school template %s from master %s for RF %s",
                tmpl.pk,
                m.pk,
                register_form.pk,
            )
            continue

        file_name = None
        if m.file and hasattr(m.file, 'name') and m.file.name:
            file_name = os.path.basename(m.file.name)
        elif m.file:
            file_name = str(m.file)
        else:
            try:
                pdf_file = generate_form_json_pdf(register_form, m.formMasterData)
                file_name = os.path.basename(pdf_file.name)
            except Exception as e:
                logger.error(f"Erreur lors de la génération du PDF pour le template: {e}")
                file_name = None

        master_file_path = m.file.path if m.file and hasattr(m.file, 'path') else None

        def _build_upload_path(template_pk):
            """Génère le chemin relatif et absolu pour un template avec un pk connu."""
            rel = registration_school_file_upload_to(
                type("Tmp", (), {
                    "registration_form": register_form,
                    "pk": template_pk,
                })(),
                file_name,
            )
            return rel, os.path.join(settings.MEDIA_ROOT, rel)

        if tmpl:
            upload_rel_path, abs_path = _build_upload_path(tmpl.pk)
            template_file_name = os.path.basename(tmpl.file.name) if tmpl.file and tmpl.file.name else None
            master_file_changed = template_file_name != file_name
            # --- GESTION FORM EXISTANT : suppression ancien template si nom ou contenu master changé ---
            if master_file_changed or (
                master_file_path and os.path.exists(master_file_path) and
                (not tmpl.file or not os.path.exists(abs_path) or os.path.getmtime(master_file_path) > os.path.getmtime(abs_path))
            ):
                # Supprimer l'ancien fichier du template (même si le nom change)
                if tmpl.file and tmpl.file.name:
                    old_template_path = os.path.join(settings.MEDIA_ROOT, tmpl.file.name)
                    if os.path.exists(old_template_path):
                        try:
                            os.remove(old_template_path)
                            logger.info(f"util.create_templates_for_registration_form - Suppression ancien fichier template: {old_template_path}")
                        except Exception as e:
                            logger.error(f"Erreur suppression ancien fichier template: {e}")
                # Copier le nouveau fichier du master (form existant)
                if master_file_path and os.path.exists(master_file_path):
                    try:
                        os.makedirs(os.path.dirname(abs_path), exist_ok=True)
                        import shutil
                        shutil.copy2(master_file_path, abs_path)
                        logger.info(f"util.create_templates_for_registration_form - Copie du fichier master {master_file_path} -> {abs_path}")
                        tmpl.file.name = upload_rel_path
                        tmpl.name = m.name or ""
                        tmpl.slug = slug
                        tmpl.formTemplateData = m.formMasterData or []
                        tmpl.save()
                    except Exception as e:
                        logger.error(f"Erreur lors de la copie du fichier master pour mise à jour du template: {e}")
            created.append(tmpl)
            logger.info("util.create_templates_for_registration_form - Mise à jour school template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk)
            continue

        # Sinon, création du template — sauvegarder d'abord pour obtenir un pk
        tmpl = RegistrationSchoolFileTemplate(
            master=m,
            registration_form=register_form,
            name=m.name or "",
            formTemplateData=m.formMasterData or [],
            slug=slug,
        )
        tmpl.save()  # pk attribué ici
        if file_name:
            upload_rel_path, abs_path = _build_upload_path(tmpl.pk)
            # Copier le fichier du master si besoin
            if master_file_path and not os.path.exists(abs_path):
                try:
                    import shutil
                    os.makedirs(os.path.dirname(abs_path), exist_ok=True)
                    shutil.copy2(master_file_path, abs_path)
                    logger.info(f"util.create_templates_for_registration_form - Copie du fichier master {master_file_path} -> {abs_path}")
                except Exception as e:
                    logger.error(f"Erreur lors de la copie du fichier master pour le template: {e}")
            tmpl.file.name = upload_rel_path
            tmpl.save()
        created.append(tmpl)
        logger.info("util.create_templates_for_registration_form - Created school template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk)

    # Créer les parent templates manquants
    for m in parent_masters:
        exists = RegistrationParentFileTemplate.objects.filter(master=m, registration_form=register_form).exists()
        if exists:
            continue
        tmpl = RegistrationParentFileTemplate.objects.create(
            master=m,
            registration_form=register_form,
            file=None,
        )
        created.append(tmpl)
        logger.info("Created parent template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk)

    return created

def recupereListeFichesInscription():
    """
    Retourne la liste complète des fiches d’inscription.
    """
    context = {
        "ficheInscriptions_list": bdd.getAllObjects(RegistrationForm),
    }
    return context

def recupereListeFichesInscriptionEnAttenteSEPA():
    """
    Retourne les fiches d’inscription avec paiement SEPA en attente.
    """
    ficheInscriptionsSEPA_list = RegistrationForm.objects.filter(modePaiement="Prélèvement SEPA").filter(etat=RegistrationForm.RegistrationFormStatus['SEPA_ENVOYE'])
    return ficheInscriptionsSEPA_list

def _now():
    """
    Retourne la date et l’heure en cours, avec fuseau.
    """
    return datetime.now(ZoneInfo(settings.TZ_APPLI))

def convertToStr(dateValue, dateFormat):
    """
    Convertit un objet datetime en chaîne selon un format donné.
    """
    return dateValue.strftime(dateFormat)

def convertToDate(date_time):
    """
    Convertit une chaîne en objet datetime selon le format '%d-%m-%Y %H:%M'.
    """
    format = '%d-%m-%Y %H:%M'
    datetime_str = datetime.strptime(date_time, format)

    return datetime_str

def convertTelephone(telephoneValue, separator='-'):
    """
    Reformate un numéro de téléphone en y insérant un séparateur donné.
    """
    return f"{telephoneValue[:2]}{separator}{telephoneValue[2:4]}{separator}{telephoneValue[4:6]}{separator}{telephoneValue[6:8]}{separator}{telephoneValue[8:10]}"

def genereRandomCode(length):
    """
    Génère un code aléatoire de longueur spécifiée.
    """
    return ''.join(random.choice(string.ascii_letters) for i in range(length))

def calculeDatePeremption(_start, nbDays):
    """
    Calcule la date de fin à partir d’un point de départ et d’un nombre de jours.
    """
    return convertToStr(_start + timedelta(days=nbDays), settings.DATE_FORMAT)

# Fonction permettant de retourner la valeur du QueryDict
# QueryDict [ index ] -> Dernière valeur d'une liste
# dict (QueryDict [ index ]) -> Toutes les valeurs de la liste
def _(liste):
    """
    Retourne la première valeur d’une liste extraite d’un QueryDict.
    """
    return liste[0]

def getArgFromRequest(_argument, _request):
    """
    Extrait la valeur d’un argument depuis la requête (JSON).
    """
    resultat = None
    data=JSONParser().parse(_request)
    resultat = data[_argument]
    return resultat

def merge_files_pdf(file_paths):
    """
    Fusionne plusieurs fichiers PDF et retourne le contenu fusionné en mémoire.
    Les fichiers non-PDF (images) sont convertis en PDF avant fusion.
    Les fichiers invalides sont ignorés avec un log d'erreur.
    """
    merger = PdfMerger()
    files_added = 0

    # Extensions d'images supportées
    image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif'}

    for file_path in file_paths:
        # Vérifier que le fichier existe
        if not os.path.exists(file_path):
            logger.warning(f"[merge_files_pdf] Fichier introuvable, ignoré: {file_path}")
            continue

        file_ext = os.path.splitext(file_path)[1].lower()

        # Si c'est une image, la convertir en PDF
        if file_ext in image_extensions:
            try:
                from PIL import Image
                from reportlab.lib.utils import ImageReader

                img = Image.open(file_path)
                img_width, img_height = img.size

                # Créer un PDF en mémoire avec l'image
                img_pdf = BytesIO()
                c = canvas.Canvas(img_pdf, pagesize=(img_width, img_height))
                c.drawImage(file_path, 0, 0, width=img_width, height=img_height)
                c.save()
                img_pdf.seek(0)

                merger.append(img_pdf)
                files_added += 1
                logger.debug(f"[merge_files_pdf] Image convertie et ajoutée: {file_path}")
            except Exception as e:
                logger.error(f"[merge_files_pdf] Erreur lors de la conversion de l'image {file_path}: {e}")
            continue

        # Sinon, essayer de l'ajouter comme PDF
        try:
            # Valider que c'est un PDF lisible
            with open(file_path, 'rb') as f:
                PdfReader(f, strict=False)

            # Si la validation passe, ajouter au merger
            merger.append(file_path)
            files_added += 1
            logger.debug(f"[merge_files_pdf] PDF ajouté: {file_path}")
        except PdfReadError as e:
            logger.error(f"[merge_files_pdf] Fichier PDF invalide, ignoré: {file_path} - {e}")
        except Exception as e:
            logger.error(f"[merge_files_pdf] Erreur lors de la lecture du fichier {file_path}: {e}")

    if files_added == 0:
        logger.warning("[merge_files_pdf] Aucun fichier valide à fusionner")
        # Retourner un PDF vide
        empty_pdf = BytesIO()
        c = canvas.Canvas(empty_pdf, pagesize=A4)
        c.drawString(100, 750, "Aucun document à afficher")
        c.save()
        empty_pdf.seek(0)
        return empty_pdf

    # Sauvegarder le fichier fusionné en mémoire
    merged_pdf = BytesIO()
    merger.write(merged_pdf)
    merger.close()

    # Revenir au début du fichier en mémoire
    merged_pdf.seek(0)

    return merged_pdf

def rfToPDF(registerForm, filename):
    """
    Génère le PDF d'un dossier d'inscription et l'associe au RegistrationForm.
    """
    filename = filename.replace(" ", "_")
    data = {
        'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}",
        'signatureDate': convertToStr(_now(), '%d-%m-%Y'),
        'signatureTime': convertToStr(_now(), '%H:%M'),
        'student': registerForm.student,
        'establishment': registerForm.establishment,
        'school_year': registerForm.school_year,
    }

    # Générer le PDF
    pdf = renderers.render_to_pdf('pdfs/fiche_eleve.html', data)
    if not pdf:
        raise ValueError("Erreur lors de la génération du PDF.")

    # Enregistrer directement le fichier dans le champ registration_file (écrase l'existant)
    try:
        save_file_replacing_existing(
            registerForm.registration_file,
            os.path.basename(filename),
            File(BytesIO(pdf.content)),
            save=True
        )
    except Exception as e:
        logger.error(f"Erreur lors de la sauvegarde du fichier PDF : {e}")
        raise

    return registerForm.registration_file

def generateRegistrationPDF(registerForm):
    """
    Génère le PDF d'un dossier d'inscription à la volée et retourne le contenu binaire.
    Ne sauvegarde pas le fichier sur disque.
    """
    data = {
        'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}",
        'signatureDate': convertToStr(_now(), '%d-%m-%Y'),
        'signatureTime': convertToStr(_now(), '%H:%M'),
        'student': registerForm.student,
        'establishment': registerForm.establishment,
        'school_year': registerForm.school_year,
    }
    pdf = renderers.render_to_pdf('pdfs/fiche_eleve.html', data)
    if not pdf:
        raise ValueError("Erreur lors de la génération du PDF.")
    return pdf.content

def delete_registration_files(registerForm):
    """
    Supprime le fichier et le dossier associés à un RegistrationForm.
    """
    base_dir = f"registration_files/dossier_rf_{registerForm.pk}"
    if registerForm.registration_file and os.path.exists(registerForm.registration_file.path):
        os.remove(registerForm.registration_file.path)
        registerForm.registration_file.delete(save=False)

    if os.path.exists(base_dir):
        shutil.rmtree(base_dir)

from datetime import datetime

def getCurrentSchoolYear():
    """
    Retourne l'année scolaire en cours au format "YYYY-YYYY".
    Exemple : Si nous sommes en octobre 2023, retourne "2023-2024".
    """
    now = datetime.now()
    current_year = now.year
    current_month = now.month

    # Si nous sommes avant septembre, l'année scolaire a commencé l'année précédente
    start_year = current_year if current_month >= 9 else current_year - 1
    return f"{start_year}-{start_year + 1}"

def getNextSchoolYear():
    """
    Retourne l'année scolaire suivante au format "YYYY-YYYY".
    Exemple : Si nous sommes en octobre 2023, retourne "2024-2025".
    """
    current_school_year = getCurrentSchoolYear()
    start_year, end_year = map(int, current_school_year.split('-'))
    return f"{start_year + 1}-{end_year + 1}"


def getHistoricalYears(count=5):
    """
    Retourne un tableau des années scolaires passées au format "YYYY-YYYY".
    Exemple : ["2022-2023", "2021-2022", "2020-2021"].
    :param count: Le nombre d'années scolaires passées à inclure.
    """
    current_school_year = getCurrentSchoolYear()
    start_year = int(current_school_year.split('-')[0])

    historical_years = []
    for i in range(1, count + 1):
        historical_start_year = start_year - i
        historical_years.append(f"{historical_start_year}-{historical_start_year + 1}")

    return historical_years

def generate_form_json_pdf(register_form, form_json, base_pdf_content=None, base_file_ext=None, base_pdf_path=None):
    """
    Génère un PDF composite du formulaire dynamique:
    - le document source uploadé (PDF/image) si présent,
    - puis un rendu du formulaire (similaire à l'aperçu),
    - avec overlay de signature(s) sur la dernière page du document source.
    """

    form_name = (form_json.get("title") or "formulaire").strip().replace(" ", "_")
    filename = f"{form_name}.pdf"
    fields = form_json.get("fields", []) if isinstance(form_json, dict) else []

    # Compatibilité ascendante : charger depuis un chemin si nécessaire
    if base_pdf_content is None and base_pdf_path and os.path.exists(base_pdf_path):
        base_file_ext = os.path.splitext(base_pdf_path)[1].lower()
        try:
            with open(base_pdf_path, 'rb') as f:
                base_pdf_content = f.read()
        except Exception as e:
            logger.error(f"[generate_form_json_pdf] Lecture fichier source: {e}")

    writer = PdfWriter()
    has_source_document = False
    source_is_image = False
    source_image_reader = None
    source_image_size = None

    # 1) Charger le document source (PDF/image) si présent
    if base_pdf_content:
        try:
            image_exts = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp'}
            ext = (base_file_ext or '').lower()

            if ext in image_exts:
                # Pour les images, on les rend dans la section [fichier uploade]
                # au lieu de les ajouter comme page source separee.
                img_buffer = BytesIO(base_pdf_content)
                source_image_reader = ImageReader(img_buffer)
                source_image_size = source_image_reader.getSize()
                source_is_image = True
            else:
                source_reader = PdfReader(BytesIO(base_pdf_content))
                for page in source_reader.pages:
                    writer.add_page(page)
                has_source_document = len(source_reader.pages) > 0
        except Exception as e:
            logger.error(f"[generate_form_json_pdf] Erreur chargement source: {e}")

    # 2) Overlay des signatures sur la dernière page du document source
    # Desactive ici pour eviter les doublons: la signature est rendue
    # dans la section JSON du formulaire (et non plus en overlay source).
    signatures = []
    for field in fields:
        if field.get("type") == "signature":
            value = field.get("value")
            if isinstance(value, str) and value.startswith("data:image"):
                signatures.append(value)

    enable_source_signature_overlay = False
    if signatures and len(writer.pages) > 0 and enable_source_signature_overlay:
        try:
            target_page = writer.pages[len(writer.pages) - 1]
            page_width = float(target_page.mediabox.width)
            page_height = float(target_page.mediabox.height)

            packet = BytesIO()
            c_overlay = canvas.Canvas(packet, pagesize=(page_width, page_height))

            sig_width = 170
            sig_height = 70
            margin = 36
            spacing = 10

            for i, data_url in enumerate(signatures[:3]):
                try:
                    x = page_width - sig_width - margin
                    y = margin + i * (sig_height + spacing)
                    _draw_signature_data_url(c_overlay, data_url, x, y, sig_width, sig_height)
                except Exception as e:
                    logger.error(f"[generate_form_json_pdf] Signature ignorée: {e}")

            c_overlay.save()
            packet.seek(0)
            overlay_pdf = PdfReader(packet)
            if overlay_pdf.pages:
                target_page.merge_page(overlay_pdf.pages[0])
        except Exception as e:
            logger.error(f"[generate_form_json_pdf] Erreur overlay signature: {e}")

    # 3) Rendu JSON explicite du formulaire final (toujours genere).
    # Cela garantit la presence des sections H1 / FileUpload / Signature
    # dans le PDF final, meme si un document source est fourni.
    fields_to_render = fields

    if fields_to_render:
        layout_buffer = BytesIO()
        c = canvas.Canvas(layout_buffer, pagesize=A4)
        y = 800

        c.setFont("Helvetica-Bold", 18)
        c.drawString(60, y, form_json.get("title", "Formulaire"))
        y -= 35

        c.setFont("Helvetica", 11)
        for field in fields_to_render:
            ftype = field.get("type", "")
            if ftype in {"heading1", "heading2", "heading3", "heading4", "heading5", "heading6", "paragraph"}:
                text = field.get("text", "")
                if text:
                    c.setFont("Helvetica-Bold" if ftype.startswith("heading") else "Helvetica", 11)
                    c.drawString(60, y, text[:120])
                    y -= 18
                    c.setFont("Helvetica", 11)
                continue

            label = field.get("label", field.get("id", "Champ"))
            value = field.get("value", "")

            if ftype == "file":
                c.drawString(60, y, f"{label}")
                y -= 18

                if source_is_image and source_image_reader and source_image_size:
                    img_w, img_h = source_image_size
                    max_w = 420
                    max_h = 260
                    ratio = min(max_w / img_w, max_h / img_h)
                    draw_w = img_w * ratio
                    draw_h = img_h * ratio

                    if y - draw_h < 80:
                        c.showPage()
                        y = 800
                        c.setFont("Helvetica", 11)

                    c.drawImage(
                        source_image_reader,
                        60,
                        y - draw_h,
                        width=draw_w,
                        height=draw_h,
                        preserveAspectRatio=True,
                        mask='auto',
                    )
                    y -= draw_h + 14
            elif ftype == "signature":
                c.drawString(60, y, f"{label}")
                sig_drawn = False
                if isinstance(value, str) and value.startswith("data:image"):
                    try:
                        sig_drawn = _draw_signature_data_url(c, value, 260, y - 55, 170, 55)
                    except Exception as e:
                        logger.error(f"[generate_form_json_pdf] Signature render echoue: {e}")
                if not sig_drawn:
                    c.rect(260, y - 55, 170, 55)
                y -= 70
            else:
                if value not in (None, ""):
                    c.drawString(60, y, f"{label} [{ftype}] : {str(value)[:120]}")
                else:
                    c.drawString(60, y, f"{label} [{ftype}]")
                y -= 18

            if y < 80:
                c.showPage()
                y = 800
                c.setFont("Helvetica", 11)

        c.save()
        layout_buffer.seek(0)
        try:
            layout_reader = PdfReader(layout_buffer)
            for page in layout_reader.pages:
                writer.add_page(page)
        except Exception as e:
            logger.error(f"[generate_form_json_pdf] Erreur ajout rendu formulaire: {e}")

    # 4) Fallback minimal si aucune page n'a été créée
    if len(writer.pages) == 0:
        fallback = BytesIO()
        c_fb = canvas.Canvas(fallback, pagesize=A4)
        c_fb.setFont("Helvetica-Bold", 16)
        c_fb.drawString(60, 800, form_json.get("title", "Formulaire"))
        c_fb.save()
        fallback.seek(0)
        fallback_reader = PdfReader(fallback)
        for page in fallback_reader.pages:
            writer.add_page(page)

    out = BytesIO()
    writer.write(out)
    out.seek(0)
    return ContentFile(out.read(), name=os.path.basename(filename))
