# Create your views here.
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils import timezone
from django.conf import settings
from django.db import transaction
from django.contrib.auth.models import User
from django.core.mail import send_mail
import logging
import json
import base64
import hashlib
from io import BytesIO
from PIL import Image
import sys
import io
import os
from datetime import datetime, timedelta
from django.core.paginator import Paginator
from django.db.models import Q
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt

# Create your views here.
from .models import (
    ProcesVerbal, Client, Participant,ControleResultat, Photo, Recommandation, TypePV, Ouvrage, ElementOuvrage, PointControle, Projets
)
from .forms import (
    ProcesVerbalForm, ControleResultatFormSet, PhotoFormSet, 
    RecommandationFormSet, ParticipantFormSet, SignatureForm,
    EmailForm
)

app_name = 'controle'

# Configuration du logger
logger = logging.getLogger(__name__)

# Forcer l'encodage UTF-8 pour cette vue
# if hasattr(sys, 'stdout'):
#    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') if hasattr(sys.stdout, 'buffer') else sys.stdout
#    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') if hasattr(sys.stderr, 'buffer') else sys.stderr

def login_view(request):
    """
    Vue personnalisée de connexion avec fonctionnalités avancées
    """
    # Si l'utilisateur est déjà connecté, on ne devrait pas lui afficher le formulaire.
    # En revanche il peut y avoir un paramètre "next" provenant de login_required, donc
    # on tente d'abord de rediriger vers cette URL (si elle est sûre). Cela évite des
    # boucles si la vue de redirection elle-même renvoie vers le login.
    if request.user.is_authenticated:
        next_url = request.GET.get('next') or request.POST.get('next')
        if next_url and is_safe_url(next_url, request.get_host()):
            return redirect(next_url)
        return redirect('controle:liste_pv')

    # Initialiser le contexte
    context = {
        'next': request.GET.get('next', ''),
        'demo_mode': settings.DEBUG,  # Mode démo uniquement en développement
        'current_year': timezone.now().year,
    }

    # Récupérer le dernier utilisateur (pour l'affichage démo)
    if settings.DEBUG:
        try:
            last_user = User.objects.filter(is_active=True).last()
            if last_user:
                context['demo_user'] = last_user.username
        except:
            pass

    if request.method == 'POST':
        # Récupérer les données du formulaire
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '')
        remember = request.POST.get('remember', False)

        # Validation de base
        if not username or not password:
            messages.error(request, 'Veuillez remplir tous les champs.')
            return render(request, 'login.html', context)

        # Vérifier les tentatives de connexion (protection brute force)
        if not check_login_attempts(request, username):
            messages.error(request, 'Trop de tentatives. Réessayez dans 15 minutes.')
            logger.warning(f"Trop de tentatives de connexion pour l'utilisateur: {username} depuis IP: {get_client_ip(request)}")
            return render(request, 'login.html', context)

        # Authentifier l'utilisateur
        user = authenticate(request, username=username, password=password)

        if user is not None:
            if user.is_active:
                # Connexion réussie
                auth_login(request, user)

                # Gérer la session "Se souvenir de moi"
                if not remember:
                    # Session expire à la fermeture du navigateur
                    request.session.set_expiry(0)
                else:
                    # Session dure 2 semaines
                    request.session.set_expiry(1209600)  # 2 semaines en secondes

                # Enregistrer la connexion
                save_login_log(request, user)

                # Message de bienvenue personnalisé
                messages.success(request, f'Bienvenue {user.get_full_name() or user.username}!')

                # Journaliser la connexion
                logger.info(f"Connexion réussie: {user.username} depuis IP: {get_client_ip(request)}")

                # Redirection vers l'URL demandée (ou défaut)
                next_url = request.POST.get('next') or request.GET.get('next') or 'controle:liste_pv'
                if not is_safe_url(next_url, request.get_host()):
                    next_url = 'controle:liste_pv'
                return redirect(next_url)
            else:
                messages.error(request, 'Ce compte est désactivé.')
                logger.warning(f"Tentative de connexion sur compte désactivé: {username}")
        else:
            # Échec de connexion
            messages.error(request, 'Nom d\'utilisateur ou mot de passe incorrect.')
            logger.warning(f"Échec de connexion pour: {username} depuis IP: {get_client_ip(request)}")

            # Incrémenter le compteur de tentatives
            increment_login_attempts(request, username)

    return render(request, 'login.html', context)


def logout_view(request):
    """
    Vue personnalisée de déconnexion
    """
    if request.user.is_authenticated:
        username = request.user.username
        auth_logout(request)
        messages.success(request, 'Vous avez été déconnecté avec succès.')
        logger.info(f"Déconnexion: {username} depuis IP: {get_client_ip(request)}")
    else:
        messages.info(request, 'Vous n\'étiez pas connecté.')
    
    return redirect('controle:login')

@login_required
def profile_view(request):
    """
    Vue du profil utilisateur
    """
    if request.method == 'POST':
        # Mise à jour du profil
        user = request.user
        email = request.POST.get('email', '')
        first_name = request.POST.get('first_name', '')
        last_name = request.POST.get('last_name', '')
        
        if email:
            user.email = email
        if first_name:
            user.first_name = first_name
        if last_name:
            user.last_name = last_name
        
        user.save()
        messages.success(request, 'Profil mis à jour avec succès.')
        
        return redirect('cores:profile')
    
    # Récupérer l'historique des connexions
    login_history = get_login_history(request.user)
    
    context = {
        'user': request.user,
        'login_history': login_history,
    }
    return render(request, 'profile.html', context)


@login_required
def change_password_view(request):
    """
    Vue pour changer le mot de passe
    """
    if request.method == 'POST':
        old_password = request.POST.get('old_password', '')
        new_password1 = request.POST.get('new_password1', '')
        new_password2 = request.POST.get('new_password2', '')
        
        user = request.user
        
        # Vérifier l'ancien mot de passe
        if not user.check_password(old_password):
            messages.error(request, 'Ancien mot de passe incorrect.')
            return redirect('core:change_password')
        
        # Vérifier que les nouveaux mots de passe correspondent
        if new_password1 != new_password2:
            messages.error(request, 'Les nouveaux mots de passe ne correspondent pas.')
            return redirect('core:change_password')
        
        # Vérifier la complexité du mot de passe
        if not validate_password_strength(new_password1):
            messages.error(request, 'Le mot de passe doit contenir au moins 8 caractères, une majuscule, une minuscule et un chiffre.')
            return redirect('core:change_password')
        
        # Changer le mot de passe
        user.set_password(new_password1)
        user.save()
        
        # Reconnecter l'utilisateur
        auth_login(request, user)
        
        messages.success(request, 'Mot de passe changé avec succès.')
        logger.info(f"Changement de mot de passe: {user.username}")
        
        return redirect('cores:profile')
    
    return render(request, 'change_password.html')

def password_reset_request(request):
    """
    Demande de réinitialisation de mot de passe
    """
    if request.method == 'POST':
        email = request.POST.get('email', '')
        
        try:
            user = User.objects.get(email=email, is_active=True)
            
            # Générer un token unique
            token = generate_reset_token(user)
            
            # Construire l'URL de réinitialisation
            reset_url = request.build_absolute_uri(f'reset-password/{token}/')
            
            # Envoyer l'email
            send_mail(
                'Réinitialisation de votre mot de passe',
                f'Cliquez sur ce lien pour réinitialiser votre mot de passe: {reset_url}',
                settings.DEFAULT_FROM_EMAIL,
                [email],
                fail_silently=False,
            )
            
            messages.success(request, 'Un email de réinitialisation vous a été envoyé.')
            logger.info(f"Demande de réinitialisation de mot de passe pour: {email}")
            
        except User.DoesNotExist:
            # Ne pas révéler si l'email existe ou non
            messages.success(request, 'Si cet email existe, un lien de réinitialisation a été envoyé.')
    
    return render(request, 'password_reset_request.html')

# Fonctions utilitaires

def get_client_ip(request):
    """
    Récupère l'adresse IP réelle du client
    """
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

def check_login_attempts(request, username):
    """
    Vérifie les tentatives de connexion pour prévenir le brute force
    """
    # Récupérer ou initialiser la session pour les tentatives
    attempts_key = f'login_attempts_{username}'
    block_key = f'login_blocked_{username}'
    
    # Vérifier si l'utilisateur est bloqué
    if request.session.get(block_key, False):
        block_time = request.session.get(f'{block_key}_time')
        if block_time:
            if timezone.now().timestamp() - block_time < 900:  # 15 minutes
                return False
            else:
                # Débloquer après 15 minutes
                request.session.pop(block_key, None)
                request.session.pop(f'{block_key}_time', None)
                request.session.pop(attempts_key, None)
    
    return True

def increment_login_attempts(request, username):
    """
    Incrémente le compteur de tentatives de connexion
    """
    attempts_key = f'login_attempts_{username}'
    attempts = request.session.get(attempts_key, 0) + 1
    request.session[attempts_key] = attempts
    
    # Bloquer après 5 tentatives
    if attempts >= 5:
        block_key = f'login_blocked_{username}'
        request.session[block_key] = True
        request.session[f'{block_key}_time'] = timezone.now().timestamp()
        logger.warning(f"Compte bloqué pour 15 minutes: {username}")

def save_login_log(request, user):
    """
    Enregistre l'historique des connexions
    """
    # Utiliser le cache ou la base de données pour stocker l'historique
    # Version simple avec session (pour la démo)
    log_key = f'login_history_{user.id}'
    log_entry = {
        'ip': get_client_ip(request),
        'user_agent': request.META.get('HTTP_USER_AGENT', ''),
        'timestamp': timezone.now().isoformat(),
        'success': True
    }
    
    history = request.session.get(log_key, [])
    history.insert(0, log_entry)
    # Garder seulement les 10 dernières connexions
    request.session[log_key] = history[:10]        

def get_login_history(user):
    """
    Récupère l'historique des connexions
    """
    from django.contrib.sessions.models import Session
    from django.contrib.auth.models import User
    
    # Version simplifiée - à améliorer selon vos besoins
    return []

def validate_password_strength(password):
    """
    Valide la force du mot de passe
    """
    if len(password) < 8:
        return False
    
    has_upper = any(c.isupper() for c in password)
    has_lower = any(c.islower() for c in password)
    has_digit = any(c.isdigit() for c in password)
    
    return has_upper and has_lower and has_digit

def generate_reset_token(user):
    """
    Génère un token unique pour la réinitialisation de mot de passe
    """
    import secrets
    import hashlib
    
    # Créer un token basé sur l'utilisateur et un random
    base = f"{user.id}-{user.password}-{secrets.token_hex(16)}-{timezone.now().timestamp()}"
    token = hashlib.sha256(base.encode()).hexdigest()
    
    # Stocker le token en session (version simple)
    # Idéalement, utilisez une table en base de données
    request = None  # À passer en paramètre dans une vraie implémentation
    
    return token

def is_safe_url(url, host):
    """
    Vérifie si l'URL de redirection est sûre
    """
    from django.utils.http import url_has_allowed_host_and_scheme
    return url_has_allowed_host_and_scheme(url, allowed_hosts={host})

@require_http_methods(["GET", "POST"])
def rechercher_ajouter_projet(request):
    if request.method == "GET":
        # Rechercher un projet
        query = request.GET.get('q', '')
        projets = Projets.objects.filter(nom__icontains=query)[:10]
        results = [{'id': p.id, 'nom': p.nom, 'description': p.description} for p in projets]
        return JsonResponse({'results': results})
    
    elif request.method == "POST":
        # Ajouter un nouveau projet
        try:
            data = json.loads(request.body)
            nom = data.get('nom')
            description = data.get('description', '')
            
            if not nom:
                return JsonResponse({'error': 'Le nom du projet est requis'}, status=400)
            
            projet, created = Projets.objects.get_or_create(
                nom=nom,
                defaults={'description': description}
            )
            
            return JsonResponse({
                'id': projet.id,
                'nom': projet.nom,
                'description': projet.description,
                'created': created
            })
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)

@login_required
def liste_pv(request):
    pvs = ProcesVerbal.objects.all().select_related('client', 'type_pv', 'createur')
    return render(request, 'pv/liste.html', {'pvs': pvs})

@login_required
def detail_pv(request, pk):
    pv = get_object_or_404(
        ProcesVerbal.objects
        .select_related(
            'client',           # Relation directe
            'type_pv'           # Relation directe
        )
        .prefetch_related(
            # Précharger les contrôles
            'controles',
            # Précharger la hiérarchie complète des points de contrôle
            'controles__point_controle',
            'controles__point_controle__element',
            'controles__point_controle__element__ouvrage',
            # Précharger les photos des contrôles
            'controles__photos',
            # Précharger les participants
            'participants',
            # Précharger les recommandations
            'recommandations'
        ),
        pk=pk
    )
    
    # Optionnel: Organiser les contrôles par ouvrage pour le template
    controles_par_ouvrage = {}
    for controle in pv.controles.all():
        if controle.point_controle and controle.point_controle.element:
            ouvrage = controle.point_controle.element.ouvrage
            if ouvrage not in controles_par_ouvrage:
                controles_par_ouvrage[ouvrage] = []
            controles_par_ouvrage[ouvrage].append(controle)
    
    context = {
        'pv': pv,
        'controles_par_ouvrage': controles_par_ouvrage,  # Optionnel
    }
    return render(request, 'pv/detail.html', context)

@login_required
@transaction.atomic
def creer_pv(request):
    """Création d'un procès-verbal vierge.
    
    Le wizard multi-étapes est implémenté entièrement côté client.
    Les points de contrôle sont basés sur les modèles ElementOuvrage et PointControle.
    """
    # Récupérer tous les éléments d'ouvrage avec leurs points de contrôle (commun aux deux méthodes)
    elements_ouvrage = ElementOuvrage.objects.filter(
        ouvrage__est_actif=True
    ).select_related('ouvrage').prefetch_related('points_controle').order_by('ouvrage__code', 'code')
    projets = Projets.objects.select_related('client').order_by('nom_projet')

    # Initialiser les variables par défaut
    form = None
    participant_formset = None

    # Fonction utilitaire pour nettoyer l'encodage
    def clean_encoding(data):
        """Nettoie l'encodage des données POST"""
        cleaned_data = {}
        for key, value in data.items():
            if isinstance(value, str):
                try:
                    # Essayer de convertir en UTF-8 si nécessaire
                    cleaned_data[key] = value.encode('latin1').decode('utf-8')
                except (UnicodeEncodeError, UnicodeDecodeError, AttributeError):
                    try:
                        cleaned_data[key] = value.encode('utf-8').decode('utf-8')
                    except (UnicodeEncodeError, UnicodeDecodeError, AttributeError):
                        cleaned_data[key] = value
            elif isinstance(value, list):
                cleaned_data[key] = [clean_encoding({str(i): v})[str(i)] if isinstance(v, str) else v for i, v in enumerate(value)]
            else:
                cleaned_data[key] = value
        return cleaned_data

    if request.method == 'POST':
        # Créer une copie mutable des données POST
        post_data = request.POST.copy()
        
        # Nettoyer l'encodage des données POST
        cleaned_post = clean_encoding(dict(post_data.items()))
        
        # Recréer post_data avec les données nettoyées
        for key, value in cleaned_post.items():
            if key in post_data:
                post_data[key] = value
        
        # Debug: Afficher toutes les données POST (optionnel, à commenter en production)
        print("=== DONNÉES POST (encodage nettoyé) ===")
        for key, value in post_data.items():
            if isinstance(value, str) and len(value) < 200:
                print(f"{key}: {value}")
            elif isinstance(value, str):
                print(f"{key}: {value[:100]}...")
            else:
                print(f"{key}: {value}")

        # Nettoyer spécifiquement le formset si présent
        if 'participants-TOTAL_FORMS' in post_data:
            for i in range(int(post_data.get('participants-TOTAL_FORMS', 0))):
                nom_key = f'participants-{i}-nom'
                if nom_key in post_data and post_data[nom_key]:
                    try:
                        post_data[nom_key] = post_data[nom_key].strip()
                    except Exception as e:
                        logger.warning(f"Erreur nettoyage participant {i}: {e}")

        form = ProcesVerbalForm(post_data)
        pv_instance = ProcesVerbal()
        participant_formset = ParticipantFormSet(post_data, instance=pv_instance, prefix='participants')

        # Debug: Vérifier les données du formset
        print("=== FORMSET DATA ===")
        print(f"TOTAL_FORMS: {post_data.get('participants-TOTAL_FORMS')}")
        for i in range(int(post_data.get('participants-TOTAL_FORMS', 0))):
            nom_value = post_data.get(f'participants-{i}-nom', '')
            signature_value = post_data.get(f'participants-{i}-signature', False)
            print(f"Participant {i}: nom={nom_value[:50] if nom_value else ''}, signature={signature_value}")
        
        if form.is_valid():
            # Vérifier la validité du formset
            if participant_formset.is_valid():
                try:
                    # Création du PV
                    pv = form.save(commit=False)
                    pv.createur = request.user
                    
                    # Générer un numéro unique
                    from django.utils import timezone
                    today = timezone.now()
                    
                    pv_count = ProcesVerbal.objects.filter(
                        date_creation__year=today.year,
                        date_creation__month=today.month
                    ).count()
                    
                    pv.numero = f"PV-{today.strftime('%Y%m')}-{pv_count + 1:04d}"
                    
                    # Nettoyer les observations générales si présentes
                    if hasattr(pv, 'observations_generales') and pv.observations_generales:
                        pv.observations_generales = pv.observations_generales.strip()
                    
                    pv.save()

                    # Lier le formset au PV et sauvegarder les participants
                    participant_formset.instance = pv
                    participant_formset.save()

                    # Traitement des résultats de contrôle
                    controles_data = {}
                    for key in post_data:
                        if key.startswith('controle_') and key.endswith('_statut'):
                            parts = key.split('_')
                            if len(parts) >= 4:
                                element_id = parts[1]
                                point_id = parts[2]
                                
                                composite_key = f"{element_id}_{point_id}"
                                if composite_key not in controles_data:
                                    controles_data[composite_key] = {
                                        'element_id': element_id,
                                        'point_id': point_id
                                    }
                                
                                # Nettoyer la valeur du statut
                                statut_value = post_data[key]
                                if isinstance(statut_value, str):
                                    statut_value = statut_value.strip()
                                controles_data[composite_key]['statut'] = statut_value
                                
                                # Récupérer l'observation correspondante
                                obs_key = f"controle_{element_id}_{point_id}_observation"
                                if obs_key in post_data:
                                    obs_value = post_data[obs_key]
                                    if isinstance(obs_value, str):
                                        obs_value = obs_value.strip()
                                    controles_data[composite_key]['observation'] = obs_value

                    # Création des résultats de contrôle
                    from .models import ControleResultat
                    
                    controles_crees = 0
                    for key, data in controles_data.items():
                        if 'statut' in data and data['statut']:
                            try:
                                point_controle = PointControle.objects.get(id=data['point_id'])
                                
                                # Nettoyer l'observation si présente
                                observation = data.get('observation', '')
                                if isinstance(observation, str):
                                    observation = observation.strip()
                                
                                controle_obj, created = ControleResultat.objects.update_or_create(
                                    pv=pv,
                                    point_controle=point_controle,
                                    defaults={
                                        'statut': data['statut'],
                                        'observation': observation
                                    }
                                )

                                # Gérer la photo envoyée pour ce point (si présente)
                                photo_field_name = f"controle_{data['element_id']}_{data['point_id']}_photo"
                                photo_file = request.FILES.get(photo_field_name)
                                if photo_file:
                                    try:
                                        Photo.objects.create(controle=controle_obj, image=photo_file)
                                    except Exception as e:
                                        logger.exception(f"Erreur en sauvegardant la photo pour {photo_field_name}: {e}")

                                controles_crees += 1
                            except PointControle.DoesNotExist:
                                logger.warning(f"Point de contrôle {data['point_id']} non trouvé")
                                continue
                            except Exception as e:
                                logger.exception(f"Erreur création contrôle pour {key}: {e}")

                    # Traitement des observations générales
                    if hasattr(pv, 'observations_generales'):
                        obs_generales = post_data.get('observations_generales', '')
                        if isinstance(obs_generales, str):
                            obs_generales = obs_generales.strip()
                        pv.observations_generales = obs_generales
                        pv.save(update_fields=['observations_generales'])

                    messages.success(
                        request, 
                        f'Procès-verbal créé avec succès. {controles_crees} points de contrôle enregistrés.'
                    )
                    return redirect('controle:liste_pv')
                    
                except Exception as e:
                    logger.exception(f'Erreur lors de la création du procès-verbal: {str(e)}')
                    # Réinitialiser le rollback si l'erreur est capturée dans l'atomic
                    if transaction.get_rollback():
                        transaction.set_rollback(False)
                    messages.error(request, f'Erreur lors de la création du procès-verbal: {str(e)}')
                    context = {
                        'form': form,
                        'participant_formset': participant_formset,
                        'elements_ouvrage': elements_ouvrage,
                        'projets': projets,
                        'etat_choices': ElementOuvrage.EtatChoices.choices,
                    }
                    return render(request, 'pv/creation.html', context)
            else:
                # Erreurs du formset
                logger.warning(f'Erreurs formset participants: {participant_formset.errors}')
                for error in participant_formset.errors:
                    if error:
                        messages.error(request, f'Erreur participant: {error}')
                context = {
                    'form': form,
                    'participant_formset': participant_formset,
                    'elements_ouvrage': elements_ouvrage,
                    'projets': projets,
                    'etat_choices': ElementOuvrage.EtatChoices.choices,
                }
                return render(request, 'pv/creation.html', context)
        else:
            # Erreurs du formulaire principal
            logger.warning(f'Erreurs formulaire PV: {form.errors}')
            for field, errors in form.errors.items():
                for error in errors:
                    messages.error(request, f'Erreur dans le champ {field}: {error}')
            context = {
                'form': form,
                'participant_formset': participant_formset,
                'elements_ouvrage': elements_ouvrage,
                'projets': projets,
                'etat_choices': ElementOuvrage.EtatChoices.choices,
            }
            return render(request, 'pv/creation.html', context)
    
    # Requête GET (ou formulaire invalide, mais on arrive ici uniquement pour GET)
    if form is None:
        initial_data = {}
        client_initial = request.GET.get('client')
        if client_initial:
            try:
                # Nettoyer le paramètre GET
                client_initial = client_initial.strip()
                initial_data['client'] = client_initial
            except AttributeError:
                initial_data['client'] = client_initial
        form = ProcesVerbalForm(initial=initial_data if initial_data else None)
        # Pour un nouveau PV, utiliser une instance temporaire pour le formset inline
        participant_formset = ParticipantFormSet(instance=ProcesVerbal(), prefix='participants')

    context = {
        'form': form,
        'participant_formset': participant_formset,
        'elements_ouvrage': elements_ouvrage,
        'projets': projets,
        'etat_choices': ElementOuvrage.EtatChoices.choices,
    }

    return render(request, 'pv/creation.html', context)
    
@login_required
@transaction.atomic
def modifier_pv(request, pk):
    pv = get_object_or_404(ProcesVerbal, pk=pk)
    
    if request.method == 'POST':
        form = ProcesVerbalForm(request.POST, instance=pv)
        controle_formset = ControleResultatFormSet(request.POST, instance=pv, prefix='controles')
        recommandation_formset = RecommandationFormSet(request.POST, instance=pv, prefix='recommandations')
        participant_formset = ParticipantFormSet(request.POST, instance=pv, prefix='participants')
        
        if all([form.is_valid(), controle_formset.is_valid(), 
                recommandation_formset.is_valid(), participant_formset.is_valid()]):
            form.save()
            controle_formset.save()

            # Si des photos ont été jointes au formset (upload après sauvegarde),
            # associer chaque fichier au contrôle correspondant.
            try:
                for idx, controle_form in enumerate(controle_formset.forms):
                    file_key = f'controles-{idx}-photo'
                    photo_file = request.FILES.get(file_key)
                    if photo_file and controle_form.instance and controle_form.instance.pk:
                        try:
                            Photo.objects.create(controle=controle_form.instance, image=photo_file)
                        except Exception as e:
                            logger.exception(f"Erreur en sauvegardant la photo pour {file_key}: {e}")
            except Exception:
                # Ne pas bloquer la sauvegarde principale si association de photo échoue
                logger.exception('Erreur lors du traitement des photos jointes au formset')

            recommandation_formset.save()
            participant_formset.save()
            
            messages.success(request, 'Procès-verbal mis à jour avec succès.')
            return redirect('controle:detail_pv', pk=pv.pk)
    else:
        form = ProcesVerbalForm(instance=pv)
        controle_formset = ControleResultatFormSet(instance=pv, prefix='controles')
        recommandation_formset = RecommandationFormSet(instance=pv, prefix='recommandations')
        participant_formset = ParticipantFormSet(instance=pv, prefix='participants')
    
    # Récupérer les ouvrages et éléments pour le sélecteur
    ouvrages = Ouvrage.objects.prefetch_related('elements').all()
    
    context = {
        'pv': pv,
        'form': form,
        'controle_formset': controle_formset,
        'recommandation_formset': recommandation_formset,
        'participant_formset': participant_formset,
        'ouvrages': ouvrages,
    }
    return render(request, 'pv/modifier_pv.html', context)

@login_required
def visualiser_pv(request, pk):

    pv = get_object_or_404(ProcesVerbal.objects.prefetch_related(
        'controles__point_controle__element__ouvrage',
        'controles__photos',
        'recommandations',
        'type_pv'  # Ajout de la relation type_pv
    ), 
    pk=pk
    )

    # Debug : Vérifier ce que retourne la méthode participants()
    print(f"\n=== INFORMATIONS PARTICIPANTS ===")
    participants = pv.participants.all() if hasattr(pv.participants, 'all') else [] 
    print(f"Nombre de participants: {participants.count() if hasattr(participants, 'count') else len(participants)}")

    # Afficher les premiers participants pour debug
    for p in participants[:3]:
        print(f"Participant: {p}")
        if hasattr(p, 'nom'):
            print(f"  - Nom: {p.nom}")
        if hasattr(p, 'participant'):
            print(f"  - Participant lié: {p.participant}")

    # Debug : Vérifier si des contrôles existent
    print(f"Nombre de contrôles: {pv.controles.count()}")
    for controle in pv.controles.all():
        print(f"Contrôle: {controle.point_controle.element if controle.point_controle else 'Sans point'}")
    
    if participants:
        print(f"Nombre de participants: {len(participants) if hasattr(participants, '__len__') else 'N/A'}")
        # Afficher le premier participant pour voir sa structure
        if hasattr(participants, 'first'):
            first_participant = participants.first()
            print(f"Premier participant: {first_participant}")
            print(f"Attributs du participant: {[attr for attr in dir(first_participant) if not attr.startswith('_')][:10]}")
    else:
        print("Aucun participant trouvé")

    
    # Récupérer les recommandations pour le template
    recommandations = pv.recommandations.all()
    
    # Pour les modals de photos
    elements_controle = []
    for controle in pv.controles.all():
        if controle.photos.exists():
            elements_controle.append({
                'id': controle.id,
                'element': controle.point_controle.element if controle.point_controle else None,
                'photos': controle.photos.all()
            })
    
    # Historique des validations (si vous avez ce modèle)
    historique_validations = []
    if hasattr(pv, 'validations'):
        historique_validations = pv.validations.all()
    
    # Documents joints (si vous avez ce modèle)
    documents_joints = []
    if hasattr(pv, 'documents'):
        documents_joints = pv.documents.all()

    context = {
        'pv': pv,
        'participants': participants,
        'recommandations': recommandations,
        'elements_controle': elements_controle,
        'historique_validations': historique_validations,
        'documents_joints': documents_joints,
    }

    return render(request, 'pv/visualiser_pv.html', context)

@login_required
def supprimer_pv(request, pk):
    pv = get_object_or_404(ProcesVerbal, pk=pk)
    
    if request.method == 'POST':
        pv.delete()
        messages.success(request, 'Procès-verbal supprimé avec succès.')
        return redirect('controle:liste_pv')
    
    return render(request, 'pv/confirmation_suppression.html', {'pv': pv})

@login_required
def imprimer_pv(request, pk):
    pv = get_object_or_404(ProcesVerbal.objects.prefetch_related(
        'controles__point_controle__element__ouvrage',
        'controles__photos',
        'participant_pvs__participant',
        'recommandations'
    ), pk=pk)
    return render(request, 'pv/imprimer_pv.html', {'pv': pv})

def exporter_pv_pdf(request, pk):
    pv = get_object_or_404(ProcesVerbal.objects.prefetch_related(
        'controles__point_controle__element__ouvrage',
        'controles__photos',
        'participant_pvs__participant',
        'recommandations'
    ), pk=pk)
    
    # Générer le PDF (à implémenter avec xhtml2pdf ou reportlab)
    # Pour l'instant, on retourne une réponse simple
    return HttpResponse("Génération PDF à implémenter")

@login_required
def envoyer_pv_email(request, pk):
    pv = get_object_or_404(ProcesVerbal, pk=pk)
    
    if request.method == 'POST':
        form = EmailForm(request.POST)
        if form.is_valid():
            destinataires = [email.strip() for email in form.cleaned_data['destinataires'].split(',')]
            message_perso = form.cleaned_data['message']
            
            # Préparer le contexte pour l'email
            context = {
                'pv': pv,
                'message_perso': message_perso,
                'site_url': request.build_absolute_uri('/'),
            }
            
            # Rendre les templates email
            html_content = render_to_string('email/pv_template.html', context)
            text_content = f"Procès-verbal {pv.numero} - {pv.client.nom}"
            
            # Créer l'email
            email = EmailMultiAlternatives(
                subject=f"PV de contrôle technique - {pv.numero}",
                body=text_content,
                from_email=settings.DEFAULT_FROM_EMAIL,
                to=destinataires,
            )
            email.attach_alternative(html_content, "text/html")
            
            # Générer et attacher le PDF
            # pdf = generer_pdf_pour_email(pv)
            # email.attach(f"PV_{pv.numero}.pdf", pdf, 'application/pdf')
            
            try:
                email.send()
                pv.emails_envoyes = ', '.join(destinataires)
                pv.date_envoi = timezone.now()
                pv.save()
                
                messages.success(request, 'Email envoyé avec succès.')
            except Exception as e:
                messages.error(request, f'Erreur lors de l\'envoi: {str(e)}')
            
            return redirect('controle:detail_pv', pk=pv.pk)
    else:
        form = EmailForm()
    
    return render(request, 'pv/envoi_email.html', {'pv': pv, 'form': form})

@login_required
def ajouter_photo(request, controle_id):
    controle = get_object_or_404(ControleResultat, pk=controle_id)
    
    if request.method == 'POST' and request.FILES.get('image'):
        photo = Photo(
            controle=controle,
            image=request.FILES['image'],
            legende=request.POST.get('legende', '')
        )
        photo.save()
        
        return JsonResponse({
            'success': True,
            'photo_id': photo.id,
            'url': photo.image.url,
            'legende': photo.legende
        })
    
    return JsonResponse({'success': False}, status=400)

@login_required
def supprimer_photo(request, photo_id):
    photo = get_object_or_404(Photo, pk=photo_id)
    pv_id = photo.controle.pv.id
    
    if request.method == 'POST':
        photo.delete()
        messages.success(request, 'Photo supprimée avec succès.')
    
    return redirect('controle:modifier_pv', pk=pv_id)

@login_required
def signature_view(request, pv_id, participant_pv_id):
    pv = get_object_or_404(ProcesVerbal, pk=pv_id)
    participant_pv = get_object_or_404(ParticipantPV, pk=participant_pv_id, proces_verbal=pv)
    
    if request.method == 'POST':
        form = SignatureForm(request.POST)
        if form.is_valid():
            signature_data = form.cleaned_data['signature_data']
            
            # Décoder l'image base64
            format, imgstr = signature_data.split(';base64,')
            ext = format.split('/')[-1]
            
            # Créer le nom de fichier
            filename = f"signature_{pv.numero}_{participant_pv.participant.nom}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{ext}"
            filepath = os.path.join(settings.MEDIA_ROOT, 'signatures', filename)
            
            # Sauvegarder l'image
            with open(filepath, 'wb') as f:
                f.write(base64.b64decode(imgstr))
            
            # Mettre à jour le participant
            participant_pv.participant.signature = os.path.join('signatures', filename)
            participant_pv.participant.date_signature = timezone.now()
            participant_pv.participant.save()
            
            participant_pv.a_signe = True
            participant_pv.date_signature = timezone.now()
            participant_pv.save()
            
            messages.success(request, f'Signature de {participant_pv.participant} enregistrée avec succès.')
            return redirect('controle:detail_pv', pk=pv_id)
    else:
        form = SignatureForm()
    
    return render(request, 'pv/signature.html', {
        'pv': pv,
        'participant_pv': participant_pv,
        'form': form
    })

@login_required
def generer_pdf(request, pk):
    pv = get_object_or_404(ProcesVerbal.objects.prefetch_related(
        'controles__point_controle__element__ouvrage',
        'controles__photos',
        'participant_pvs__participant',
        'recommandations'
    ), pk=pk)
    
    # Générer le PDF (à implémenter avec xhtml2pdf ou reportlab)
    # Pour l'instant, on retourne une réponse simple
    return HttpResponse("Génération PDF à implémenter")

@login_required
def envoyer_pv_email(request, pk):
    pv = get_object_or_404(ProcesVerbal, pk=pk)
    
    if request.method == 'POST':
        form = EmailForm(request.POST)
        if form.is_valid():
            destinataires = [email.strip() for email in form.cleaned_data['destinataires'].split(',')]
            message_perso = form.cleaned_data['message']
            
            # Préparer le contexte pour l'email
            context = {
                'pv': pv,
                'message_perso': message_perso,
                'site_url': request.build_absolute_uri('/'),
            }
            
            # Rendre les templates email
            html_content = render_to_string('email/pv_template.html', context)
            text_content = f"Procès-verbal {pv.numero} - {pv.client.nom}"
            
            # Créer l'email
            email = EmailMultiAlternatives(
                subject=f"PV de contrôle technique - {pv.numero}",
                body=text_content,
                from_email=settings.DEFAULT_FROM_EMAIL,
                to=destinataires,
            )
            email.attach_alternative(html_content, "text/html")
            
            # Générer et attacher le PDF
            # pdf = generer_pdf_pour_email(pv)
            # email.attach(f"PV_{pv.numero}.pdf", pdf, 'application/pdf')
            
            try:
                email.send()
                pv.emails_envoyes = ', '.join(destinataires)
                pv.date_envoi = timezone.now()
                pv.save()
                
                messages.success(request, 'Email envoyé avec succès.')
            except Exception as e:
                messages.error(request, f'Erreur lors de l\'envoi: {str(e)}')
            
            return redirect('controle:detail_pv', pk=pv.pk)
    else:
        form = EmailForm()
    
    return render(request, 'pv/envoi_email.html', {'pv': pv, 'form': form})

# ========================
# exportation 
# =========================
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import csv
import io
import xlsxwriter


@login_required
def export_pv(request, pk):
    """
    Exporte un procès-verbal dans différents formats (PDF, Excel, CSV, JSON)
    """
    pv = get_object_or_404(ProcesVerbal.objects.prefetch_related(
        'controles__point_controle__element__ouvrage',
        'controles__photos',
        'participant_pvs__participant',
        'recommandations'
    ), pk=pk)
    
    # Récupérer le format d'export depuis les paramètres GET
    export_format = request.GET.get('format', 'pdf').lower()
    
    if export_format == 'excel' or export_format == 'xlsx':
        return export_pv_excel(request, pv)
    elif export_format == 'csv':
        return export_pv_csv(request, pv)
    elif export_format == 'json':
        return export_pv_json(request, pv)
    elif export_format == 'docx':
        return export_pv_docx(request, pv)
    else:
        # Par défaut, exporter en PDF
        return export_pv_pdf(request, pv)

def export_pv_pdf(request, pv):
    """
    Exporte un PV au format PDF avec mise en page professionnelle
    """
    from reportlab.lib.pagesizes import A4
    from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak
    from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
    from reportlab.lib.units import cm
    from reportlab.lib import colors
    from datetime import datetime
    import os
    from django.conf import settings
    
    # Créer un buffer pour le PDF
    buffer = io.BytesIO()
    
    # Configuration du document
    doc = SimpleDocTemplate(
        buffer,
        pagesize=A4,
        rightMargin=2*cm,
        leftMargin=2*cm,
        topMargin=2*cm,
        bottomMargin=2*cm,
        title=f"PV_{pv.numero}",
        author=request.user.get_full_name() or request.user.username
    )
    
    # Styles
    styles = getSampleStyleSheet()
    style_normal = styles['Normal']
    style_heading = styles['Heading1']
    style_title = styles['Title']
    
    # Style personnalisé pour les titres
    style_section = ParagraphStyle(
        'SectionTitle',
        parent=styles['Heading2'],
        fontSize=14,
        textColor=colors.HexColor('#667eea'),
        spaceAfter=12,
        spaceBefore=20,
        underline=True
    )
    
    style_subsection = ParagraphStyle(
        'SubSection',
        parent=styles['Heading3'],
        fontSize=12,
        textColor=colors.HexColor('#4a5568'),
        spaceAfter=8,
        spaceBefore=12
    )
    
    style_label = ParagraphStyle(
        'Label',
        parent=styles['Normal'],
        fontSize=10,
        textColor=colors.gray,
        alignment=2  # TA_RIGHT
    )
    
    style_value = ParagraphStyle(
        'Value',
        parent=styles['Normal'],
        fontSize=11,
        textColor=colors.black,
        alignment=0  # TA_LEFT
    )
    
    style_center = ParagraphStyle(
        'Center',
        parent=styles['Normal'],
        alignment=1  # TA_CENTER
    )
    
    style_footer = ParagraphStyle(
        'Footer',
        parent=styles['Normal'],
        fontSize=8,
        textColor=colors.grey,
        alignment=1
    )
    
    # Liste des éléments du document
    elements = []
    
    # En-tête du document
    header_data = [
        [Paragraph(f"<b>PROCÈS-VERBAL DE CONTRÔLE TECHNIQUE</b>", style_title)],
        [Paragraph(f"N° {pv.numero}", style_center)]
    ]
    header_table = Table(header_data, colWidths=[doc.width])
    header_table.setStyle(TableStyle([
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('TEXTCOLOR', (0, 0), (-1, -1), colors.HexColor('#667eea')),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 20),
    ]))
    elements.append(header_table)
    elements.append(Spacer(1, 0.5*cm))
    
    # Informations générales
    elements.append(Paragraph("INFORMATIONS GÉNÉRALES", style_section))
    
    info_data = [
        ["Type de PV:", pv.type_pv.libelle],
        ["Client:", pv.client.nom],
        ["Date de visite:", pv.date_visite.strftime('%d/%m/%Y à %H:%M')],
        ["Lieu:", pv.lieu],
        ["Créateur:", pv.createur.get_full_name() or pv.createur.username],
        ["Date création:", pv.date_creation.strftime('%d/%m/%Y %H:%M')],
    ]
    
    info_table = Table(info_data, colWidths=[doc.width*0.3, doc.width*0.7])
    info_table.setStyle(TableStyle([
        ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 10),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 8),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
        ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f7fafc')),
    ]))
    elements.append(info_table)
    elements.append(Spacer(1, 0.3*cm))
    
    # Objet du contrôle
    elements.append(Paragraph(f"<b>Objet du contrôle:</b> {pv.objet_controle}", style_normal))
    elements.append(Spacer(1, 0.3*cm))
    
    if pv.observations_generales:
        elements.append(Paragraph(f"<b>Observations générales:</b>", style_subsection))
        elements.append(Paragraph(pv.observations_generales, style_normal))
        elements.append(Spacer(1, 0.3*cm))
    
    # Participants
    if pv.participants.exists():
        elements.append(Paragraph("PARTICIPANTS", style_section))
        
        participants_data = [["Nom", "Fonction", "Email", "Signature"]]
        for participant_pv in pv.participants.all():
            participant = participant_pv.participant
            signature_status = "✓ Signé" if participant_pv.a_signe else "✗ Non signé"
            participants_data.append([
                f"{participant.prenom} {participant.nom}",
                participant.fonction or "-",
                participant.email or "-",
                signature_status
            ])
        
        participants_table = Table(participants_data, colWidths=[doc.width*0.25, doc.width*0.25, doc.width*0.25, doc.width*0.25])
        participants_table.setStyle(TableStyle([
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('FONTSIZE', (0, 0), (-1, -1), 9),
            ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#667eea')),
            ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
            ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
            ('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
            ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f7fafc')]),
        ]))
        elements.append(participants_table)
        elements.append(Spacer(1, 0.3*cm))
    
    # Éléments contrôlés
    if pv.controles.exists():
        elements.append(Paragraph("ÉLÉMENTS CONTRÔLÉS", style_section))
        
        controles_data = [["Ouvrage", "Élément", "État", "Commentaire", "Recommandation"]]
        for controle in pv.controles.all():
            etat_color = {
                'CONFORME': 'green',
                'NON_CONFORME': 'red',
                'SUSPENDU': 'orange'
            }.get(controle.etat, 'black')
            
            controles_data.append([
                controle.element.ouvrage.nom,
                controle.element.nom,
                controle.get_etat_display(),
                controle.commentaire or "-",
                controle.recommandation or "-"
            ])
        
        controles_table = Table(controles_data, colWidths=[doc.width*0.2, doc.width*0.2, doc.width*0.15, doc.width*0.25, doc.width*0.2])
        controles_table.setStyle(TableStyle([
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('FONTSIZE', (0, 0), (-1, -1), 9),
            ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#667eea')),
            ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
            ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
            ('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
            ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f7fafc')]),
        ]))
        
        # Colorer les lignes selon l'état
        for i, controle in enumerate(pv.controles.all(), start=1):
            color = colors.white
            if controle.etat == 'CONFORME':
                color = colors.HexColor('#e6ffe6')
            elif controle.etat == 'NON_CONFORME':
                color = colors.HexColor('#ffe6e6')
            elif controle.etat == 'SUSPENDU':
                color = colors.HexColor('#fff3e6')
            
            controles_table.setStyle(TableStyle([
                ('BACKGROUND', (0, i), (-1, i), color)
            ]))
        
        elements.append(controles_table)
        elements.append(Spacer(1, 0.3*cm))
    
    # Photos
    photos_exist = False
    for controle in pv.controles.all():
        if controle.photos.exists():
            photos_exist = True
            break
    
    if photos_exist:
        elements.append(Paragraph("PHOTOS", style_section))
        
        for controle in pv.controles.all():
            if controle.photos.exists():
                elements.append(Paragraph(f"<b>{controle.element.ouvrage.nom} - {controle.element.nom}</b>", style_subsection))
                
                for photo in controle.photos.all():
                    if photo.image and os.path.exists(photo.image.path):
                        try:
                            img = Image(photo.image.path, width=5*cm, height=5*cm)
                            elements.append(img)
                            if photo.legende:
                                elements.append(Paragraph(f"<i>{photo.legende}</i>", style_center))
                            elements.append(Spacer(1, 0.2*cm))
                        except:
                            elements.append(Paragraph(f"[Photo: {photo.legende or 'Sans légende'}]", style_normal))
    
    # Recommandations
    if pv.recommandations.exists():
        elements.append(PageBreak())
        elements.append(Paragraph("RECOMMANDATIONS", style_section))
        
        recommandations_data = [["N°", "Recommandation"]]
        for i, reco in enumerate(pv.recommandations.all(), 1):
            recommandations_data.append([str(i), reco.texte])
        
        reco_table = Table(recommandations_data, colWidths=[doc.width*0.1, doc.width*0.9])
        reco_table.setStyle(TableStyle([
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('FONTSIZE', (0, 0), (-1, -1), 9),
            ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#667eea')),
            ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
            ('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ]))
        elements.append(reco_table)
    
    # Pied de page
    elements.append(Spacer(1, 1*cm))
    footer_text = f"Document généré le {datetime.now().strftime('%d/%m/%Y à %H:%M')} par {request.user.get_full_name() or request.user.username}"
    elements.append(Paragraph(footer_text, style_footer))
    
    # Générer le PDF
    doc.build(elements)
    
    # Récupérer la valeur du buffer
    pdf = buffer.getvalue()
    buffer.close()
    
    # Créer la réponse HTTP
    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = f'attachment; filename="PV_{pv.numero}_{datetime.now().strftime("%Y%m%d")}.pdf"'
    response['Content-Length'] = len(pdf)
    response.write(pdf)
    
    return response

def export_pv_excel(request, pv):
    """
    Exporte un PV au format Excel
    """
    import xlsxwriter
    from io import BytesIO
    
    # Créer un buffer pour le fichier Excel
    output = BytesIO()
    
    # Créer le workbook et les worksheets
    workbook = xlsxwriter.Workbook(output, {'in_memory': True})
    
    # Formats
    header_format = workbook.add_format({
        'bold': True,
        'bg_color': '#667eea',
        'font_color': 'white',
        'border': 1,
        'align': 'center',
        'valign': 'vcenter'
    })
    
    title_format = workbook.add_format({
        'bold': True,
        'font_size': 14,
        'font_color': '#667eea',
        'align': 'left',
        'valign': 'vcenter'
    })
    
    label_format = workbook.add_format({
        'bold': True,
        'bg_color': '#f7fafc',
        'border': 1,
        'align': 'left',
        'valign': 'vcenter'
    })
    
    value_format = workbook.add_format({
        'border': 1,
        'align': 'left',
        'valign': 'vcenter'
    })
    
    date_format = workbook.add_format({'num_format': 'dd/mm/yyyy hh:mm', 'border': 1})
    
    # Worksheet 1: Informations générales
    ws1 = workbook.add_worksheet("Informations")
    ws1.set_column('A:A', 20)
    ws1.set_column('B:B', 40)
    
    # Titre
    ws1.merge_range('A1:B1', f"PROCÈS-VERBAL N° {pv.numero}", header_format)
    ws1.set_row(0, 30)
    
    # Informations générales
    row = 2
    ws1.write(row, 0, "Type de PV", label_format)
    ws1.write(row, 1, pv.type_pv.libelle, value_format)
    row += 1
    
    ws1.write(row, 0, "Client", label_format)
    ws1.write(row, 1, pv.client.nom, value_format)
    row += 1
    
    ws1.write(row, 0, "Date de visite", label_format)
    ws1.write_datetime(row, 1, pv.date_visite, date_format)
    row += 1
    
    ws1.write(row, 0, "Lieu", label_format)
    ws1.write(row, 1, pv.lieu, value_format)
    row += 1
    
    ws1.write(row, 0, "Objet du contrôle", label_format)
    ws1.write(row, 1, pv.objet_controle, value_format)
    row += 1
    
    if pv.observations_generales:
        ws1.write(row, 0, "Observations", label_format)
        ws1.write(row, 1, pv.observations_generales, value_format)
        row += 1
    
    ws1.write(row, 0, "Créé par", label_format)
    ws1.write(row, 1, pv.createur.get_full_name() or pv.createur.username, value_format)
    row += 1
    
    ws1.write(row, 0, "Date création", label_format)
    ws1.write_datetime(row, 1, pv.date_creation, date_format)
    
    # Worksheet 2: Participants
    ws2 = workbook.add_worksheet("Participants")
    ws2.set_column('A:A', 25)
    ws2.set_column('B:B', 25)
    ws2.set_column('C:C', 30)
    ws2.set_column('D:D', 15)
    
    headers = ['Nom', 'Fonction', 'Email', 'Signé']
    for col, header in enumerate(headers):
        ws2.write(0, col, header, header_format)
    
    row = 1
    for participant_pv in pv.participants.all():
        participant = participant_pv.participant
        ws2.write(row, 0, f"{participant.prenom} {participant.nom}", value_format)
        ws2.write(row, 1, participant.fonction or "-", value_format)
        ws2.write(row, 2, participant.email or "-", value_format)
        ws2.write(row, 3, "Oui" if participant_pv.a_signe else "Non", value_format)
        row += 1
    
    # Worksheet 3: Éléments contrôlés
    ws3 = workbook.add_worksheet("Contrôles")
    ws3.set_column('A:A', 25)
    ws3.set_column('B:B', 30)
    ws3.set_column('C:C', 15)
    ws3.set_column('D:D', 40)
    ws3.set_column('E:E', 40)
    
    headers = ['Ouvrage', 'Élément', 'État', 'Commentaire', 'Recommandation']
    for col, header in enumerate(headers):
        ws3.write(0, col, header, header_format)
    
    row = 1
    for controle in pv.controles.all():
        ws3.write(row, 0, controle.element.ouvrage.nom, value_format)
        ws3.write(row, 1, controle.element.nom, value_format)
        
        # Couleur selon l'état
        etat_format = workbook.add_format({'border': 1})
        if controle.etat == 'CONFORME':
            etat_format.set_bg_color('#e6ffe6')
        elif controle.etat == 'NON_CONFORME':
            etat_format.set_bg_color('#ffe6e6')
        elif controle.etat == 'SUSPENDU':
            etat_format.set_bg_color('#fff3e6')
        
        ws3.write(row, 2, controle.get_etat_display(), etat_format)
        ws3.write(row, 3, controle.commentaire or "-", value_format)
        ws3.write(row, 4, controle.recommandation or "-", value_format)
        row += 1
    
    # Worksheet 4: Recommandations
    if pv.recommandations.exists():
        ws4 = workbook.add_worksheet("Recommandations")
        ws4.set_column('A:A', 10)
        ws4.set_column('B:B', 80)
        
        ws4.write(0, 0, "N°", header_format)
        ws4.write(0, 1, "Recommandation", header_format)
        
        row = 1
        for i, reco in enumerate(pv.recommandations.all(), 1):
            ws4.write(row, 0, i, value_format)
            ws4.write(row, 1, reco.texte, value_format)
            row += 1
    
    # Worksheet 5: Photos
    photos_exist = False
    ws5 = workbook.add_worksheet("Photos")
    ws5.set_column('A:A', 20)
    ws5.set_column('B:B', 40)
    ws5.set_column('C:C', 20)
    
    ws5.write(0, 0, "Élément", header_format)
    ws5.write(0, 1, "Photo", header_format)
    ws5.write(0, 2, "Légende", header_format)
    
    row = 1
    for controle in pv.controles.all():
        for photo in controle.photos.all():
            ws5.write(row, 0, f"{controle.element.ouvrage.nom} - {controle.element.nom}", value_format)
            ws5.write(row, 1, photo.image.url if photo.image else "-", value_format)
            ws5.write(row, 2, photo.legende or "-", value_format)
            row += 1
            photos_exist = True
    
    if not photos_exist:
        ws5.write(1, 0, "Aucune photo", value_format)
    
    # Fermer le workbook
    workbook.close()
    
    # Préparer la réponse
    output.seek(0)
    response = HttpResponse(
        output.read(),
        content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )
    response['Content-Disposition'] = f'attachment; filename="PV_{pv.numero}_{datetime.now().strftime("%Y%m%d")}.xlsx"'
    
    return response

def export_pv_csv(request, pv):
    """
    Exporte un PV au format CSV
    """
    response = HttpResponse(content_type='text/csv; charset=utf-8')
    response['Content-Disposition'] = f'attachment; filename="PV_{pv.numero}_{datetime.now().strftime("%Y%m%d")}.csv"'
    
    # Ajouter le BOM pour UTF-8 (Excel)
    response.write('\ufeff'.encode('utf8'))
    
    writer = csv.writer(response, delimiter=';', quoting=csv.QUOTE_MINIMAL)
    
    # En-tête du fichier
    writer.writerow(['PROCÈS-VERBAL DE CONTRÔLE TECHNIQUE'])
    writer.writerow([f'N° {pv.numero}'])
    writer.writerow([])
    
    # Informations générales
    writer.writerow(['INFORMATIONS GÉNÉRALES'])
    writer.writerow(['Type de PV', pv.type_pv.libelle])
    writer.writerow(['Client', pv.client.nom])
    writer.writerow(['Date de visite', pv.date_visite.strftime('%d/%m/%Y %H:%M')])
    writer.writerow(['Lieu', pv.lieu])
    writer.writerow(['Objet du contrôle', pv.objet_controle])
    if pv.observations_generales:
        writer.writerow(['Observations', pv.observations_generales])
    writer.writerow(['Créateur', pv.createur.get_full_name() or pv.createur.username])
    writer.writerow(['Date création', pv.date_creation.strftime('%d/%m/%Y %H:%M')])
    writer.writerow([])
    
    # Participants
    writer.writerow(['PARTICIPANTS'])
    writer.writerow(['Nom', 'Fonction', 'Email', 'Signé'])
    for participant_pv in pv.participants.all():
        participant = participant_pv.participant
        writer.writerow([
            f"{participant.prenom} {participant.nom}",
            participant.fonction or '',
            participant.email or '',
            'Oui' if participant_pv.a_signe else 'Non'
        ])
    writer.writerow([])
    
    # Éléments contrôlés
    writer.writerow(['ÉLÉMENTS CONTRÔLÉS'])
    writer.writerow(['Ouvrage', 'Élément', 'État', 'Commentaire', 'Recommandation'])
    for controle in pv.controles.all():
        writer.writerow([
            controle.element.ouvrage.nom,
            controle.element.nom,
            controle.get_etat_display(),
            controle.commentaire or '',
            controle.recommandation or ''
        ])
    writer.writerow([])
    
    # Recommandations
    if pv.recommandations.exists():
        writer.writerow(['RECOMMANDATIONS'])
        for i, reco in enumerate(pv.recommandations.all(), 1):
            writer.writerow([f'{i}.', reco.texte])
        writer.writerow([])
    
    # Photos
    writer.writerow(['PHOTOS'])
    writer.writerow(['Élément', 'Photo', 'Légende'])
    photos_exist = False
    for controle in pv.controles.all():
        for photo in controle.photos.all():
            writer.writerow([
                f"{controle.element.ouvrage.nom} - {controle.element.nom}",
                photo.image.url if photo.image else '',
                photo.legende or ''
            ])
            photos_exist = True
    
    if not photos_exist:
        writer.writerow(['Aucune photo'])
    
    return response

def export_pv_json(request, pv):
    """
    Exporte un PV au format JSON
    """
    data = {
        'numero': pv.numero,
        'type_pv': {
            'code': pv.type_pv.code,
            'libelle': pv.type_pv.libelle
        },
        'client': {
            'nom': pv.client.nom,
            'adresse': pv.client.adresse,
            'telephone': pv.client.telephone,
            'email': pv.client.email
        },
        'date_visite': pv.date_visite.isoformat(),
        'lieu': pv.lieu,
        'objet_controle': pv.objet_controle,
        'observations_generales': pv.observations_generales or '',
        'createur': {
            'username': pv.createur.username,
            'nom_complet': pv.createur.get_full_name()
        },
        'date_creation': pv.date_creation.isoformat(),
        'participants': [],
        'controles': [],
        'recommandations': []
    }
    
    # Participants
    for participant_pv in pv.participants.all():
        participant = participant_pv.participant
        data['participants'].append({
            'nom': participant.nom,
            'prenom': participant.prenom,
            'fonction': participant.fonction,
            'email': participant.email,
            'a_signe': participant_pv.a_signe,
            'date_signature': participant_pv.date_signature.isoformat() if participant_pv.date_signature else None
        })
    
    # Éléments contrôlés
    for controle in pv.controles.all():
        controle_data = {
            'ouvrage': {
                'code': controle.element.ouvrage.code,
                'nom': controle.element.ouvrage.nom
            },
            'element': {
                'code': controle.element.code,
                'nom': controle.element.nom
            },
            'etat': controle.etat,
            'etat_display': controle.get_etat_display(),
            'commentaire': controle.commentaire or '',
            'recommandation': controle.recommandation or '',
            'photos': []
        }
        
        for photo in controle.photos.all():
            controle_data['photos'].append({
                'url': photo.image.url if photo.image else '',
                'legende': photo.legende or '',
                'date_prise': photo.date_prise.isoformat()
            })
        
        data['controles'].append(controle_data)
    
    # Recommandations
    for reco in pv.recommandations.all():
        data['recommandations'].append({
            'ordre': reco.ordre,
            'texte': reco.texte
        })
    
    response = JsonResponse(data, json_dumps_params={'indent': 2, 'ensure_ascii': False})
    response['Content-Disposition'] = f'attachment; filename="PV_{pv.numero}_{datetime.now().strftime("%Y%m%d")}.json"'
    return response

def export_pv_docx(request, pv):
    """
    Exporte un PV au format Word (DOCX) - Version simplifiée
    Note: Nécessite l'installation de python-docx
    """
    try:
        from docx import Document
        from docx.shared import Inches, Pt, RGBColor
        from docx.enum.text import WD_ALIGN_PARAGRAPH
        from docx.enum.table import WD_TABLE_ALIGNMENT
        
        document = Document()
        
        # Style du document
        style = document.styles['Normal']
        style.font.name = 'Calibri'
        style.font.size = Pt(11)
        
        # Titre principal
        title = document.add_heading(f'PROCÈS-VERBAL DE CONTRÔLE TECHNIQUE', 0)
        title.alignment = WD_ALIGN_PARAGRAPH.CENTER
        title.runs[0].font.color.rgb = RGBColor(102, 126, 234)
        
        subtitle = document.add_heading(f'N° {pv.numero}', 1)
        subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
        
        document.add_paragraph()
        
        # Informations générales
        document.add_heading('INFORMATIONS GÉNÉRALES', level=1)
        
        table = document.add_table(rows=6, cols=2)
        table.style = 'Table Grid'
        table.alignment = WD_TABLE_ALIGNMENT.CENTER
        
        # Remplir le tableau
        cells = [
            ('Type de PV:', pv.type_pv.libelle),
            ('Client:', pv.client.nom),
            ('Date de visite:', pv.date_visite.strftime('%d/%m/%Y à %H:%M')),
            ('Lieu:', pv.lieu),
            ('Objet du contrôle:', pv.objet_controle),
            ('Observations:', pv.observations_generales or 'Aucune')
        ]
        
        for i, (label, value) in enumerate(cells):
            table.cell(i, 0).text = label
            table.cell(i, 1).text = str(value)
            table.cell(i, 0).paragraphs[0].runs[0].font.bold = True
        
        document.add_paragraph()
        
        # Participants
        if pv.participants.exists():
            document.add_heading('PARTICIPANTS', level=1)
            
            table = document.add_table(rows=pv.participants.count()+1, cols=4)
            table.style = 'Table Grid'
            
            # En-têtes
            headers = ['Nom', 'Fonction', 'Email', 'Signature']
            for i, header in enumerate(headers):
                table.cell(0, i).text = header
                table.cell(0, i).paragraphs[0].runs[0].font.bold = True
            
            # Données
            for i, participant_pv in enumerate(pv.participants.all(), 1):
                participant = participant_pv.participant
                table.cell(i, 0).text = f"{participant.prenom} {participant.nom}"
                table.cell(i, 1).text = participant.fonction or '-'
                table.cell(i, 2).text = participant.email or '-'
                table.cell(i, 3).text = '✓' if participant_pv.a_signe else '✗'
            
            document.add_paragraph()
        
        # Éléments contrôlés
        if pv.controles.exists():
            document.add_heading('ÉLÉMENTS CONTRÔLÉS', level=1)
            
            table = document.add_table(rows=pv.controles.count()+1, cols=5)
            table.style = 'Table Grid'
            
            # En-têtes
            headers = ['Ouvrage', 'Élément', 'État', 'Commentaire', 'Recommandation']
            for i, header in enumerate(headers):
                table.cell(0, i).text = header
                table.cell(0, i).paragraphs[0].runs[0].font.bold = True
            
            # Données
            for i, controle in enumerate(pv.controles.all(), 1):
                table.cell(i, 0).text = controle.element.ouvrage.nom
                table.cell(i, 1).text = controle.element.nom
                table.cell(i, 2).text = controle.get_etat_display()
                table.cell(i, 3).text = controle.commentaire or '-'
                table.cell(i, 4).text = controle.recommandation or '-'
        
        # Sauvegarder le document
        from io import BytesIO
        docx_buffer = BytesIO()
        document.save(docx_buffer)
        docx_buffer.seek(0)
        
        response = HttpResponse(
            docx_buffer.read(),
            content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        )
        response['Content-Disposition'] = f'attachment; filename="PV_{pv.numero}_{datetime.now().strftime("%Y%m%d")}.docx"'
        
        return response
        
    except ImportError:
        # Si python-docx n'est pas installé
        return HttpResponse(
            "L'export Word nécessite l'installation de python-docx. Utilisez: pip install python-docx",
            content_type='text/plain',
            status=501
        )
    
@login_required
def export_pv_multiple(request):
    """
    Exporte plusieurs PV dans une archive ZIP
    """
    import zipfile
    from io import BytesIO
    
    # Récupérer les IDs des PV à exporter
    pv_ids = request.GET.getlist('ids')
    export_format = request.GET.get('format', 'pdf')
    
    if not pv_ids:
        return HttpResponse("Aucun PV sélectionné", status=400)
    
    # Créer l'archive ZIP
    zip_buffer = BytesIO()
    
    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
        for pv_id in pv_ids:
            try:
                pv = ProcesVerbal.objects.get(pk=pv_id)
                
                # Exporter selon le format
                if export_format == 'pdf':
                    response = export_pv_pdf(request, pv)
                    content = response.content
                    ext = 'pdf'
                elif export_format == 'excel':
                    response = export_pv_excel(request, pv)
                    content = response.content
                    ext = 'xlsx'
                elif export_format == 'csv':
                    response = export_pv_csv(request, pv)
                    content = response.content
                    ext = 'csv'
                elif export_format == 'json':
                    response = export_pv_json(request, pv)
                    content = response.content
                    ext = 'json'
                else:
                    continue
                
                # Ajouter à l'archive
                filename = f"PV_{pv.numero}_{datetime.now().strftime('%Y%m%d')}.{ext}"
                zip_file.writestr(filename, content)
                
            except ProcesVerbal.DoesNotExist:
                continue
    
    zip_buffer.seek(0)
    
    response = HttpResponse(zip_buffer.read(), content_type='application/zip')
    response['Content-Disposition'] = f'attachment; filename="PV_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip"'
    
    return response   

@login_required
def api_elements_ouvrage(request, ouvrage_id):
    elements = ElementOuvrage.objects.filter(ouvrage_id=ouvrage_id).values('id', 'code', 'nom')
    return JsonResponse(list(elements), safe=False)

# ========================
#  PARAMETRES
# ========================
@login_required
def parametres(request):
    """Vue principale des paramètres"""
    types_pv = TypePV.objects.all().order_by('code')
    ouvrages = Ouvrage.objects.all().prefetch_related('elements').order_by('code')
    points_controle = PointControle.objects.all().select_related('element__ouvrage').order_by('element_id')
    
    context = {
        'types_pv': types_pv,
        'ouvrages': ouvrages,
        'points_controle': points_controle,
    }
    return render(request, 'parametres/parametres.html', context)

# ========================
# Gestion des types de PV
# ========================
def ajouter_type_pv(request):
    if request.method == 'POST':
        code = request.POST.get('code')
        libelle = request.POST.get('libelle')
        
        try:
            TypePV.objects.create(code=code, libelle=libelle)
            messages.success(request, 'Type de PV ajouté avec succès')
        except Exception as e:
            messages.error(request, f'Erreur lors de l\'ajout : {str(e)}')
    
    return redirect('controle:param_listepv')

def modifier_type_pv(request):
      # Récupérer l'ID depuis les paramètres GET
    type_id = request.GET.get('id')
    
    # Vérifier si l'ID est fourni
    if not type_id:
        messages.error(request, 'ID du type de PV non spécifié')
        return redirect('liste_type_pv')
    
    # Récupérer l'objet ou afficher une erreur 404 s'il n'existe pas
    try:
        type_pv = get_object_or_404(TypePV, id=type_id)
    except:
        messages.error(request, f'Le type de PV avec l\'ID {type_id} n\'existe pas dans la base de données')
        return redirect('liste_type_pv')
    
    if request.method == 'POST':
        # Récupérer les données du formulaire
        code = request.POST.get('code')
        libelle = request.POST.get('libelle')
        description = request.POST.get('description', '')
        
        # Valider les champs obligatoires
        if not code or not libelle:
            messages.error(request, 'Le code et le libellé sont obligatoires')
            return redirect(f'{request.path}?id={type_id}')
        
        try:
            # Mettre à jour l'objet
            type_pv.code = code
            type_pv.libelle = libelle
            type_pv.description = description
            type_pv.save()
            
            messages.success(request, f'Type de PV "{type_pv.code}" modifié avec succès')
            return redirect('liste_type_pv')
            
        except Exception as e:
            messages.error(request, f'Erreur lors de la modification : {str(e)}')
            return redirect(f'{request.path}?id={type_id}')
    
    # Si c'est une requête GET, afficher le formulaire de modification
    return render(request, 'parametres/liste_pv.html')


def supprimer_type_pv(request):
    if request.method == 'GET':  # Ou POST pour plus de sécurité
        type_id = request.GET.get('id')
        
        if not type_id:
            messages.error(request, 'ID du type de PV non spécifié')
            return redirect('liste_type_pv')
        
        try:
            type_pv = get_object_or_404(TypePV, id=type_id)
            code = type_pv.code
            type_pv.delete()
            messages.success(request, f'Le type de PV "{code}" a été supprimé avec succès')
        except Exception as e:
            messages.error(request, f'Erreur lors de la suppression : {str(e)}')
    
    return redirect('controle:param_listepv')

def get_type_pv(request, type_id):
    """API pour récupérer un type de PV (pour l'édition)"""
    type_pv = get_object_or_404(TypePV, id=type_id)
    return JsonResponse({
        'id': type_pv.id,
        'code': type_pv.code,
        'type_pv': type_pv.type_pv,
    })

def liste_type_pv(request):
    typespv = TypePV.objects.all().order_by('code')
    context ={
        'typespv': typespv,
        }

    return render(request, 'parametres/liste_pv.html', context)

def exporter_pv_word(request, pv_id):
    # 1. Récupération des données
    pv = get_object_or_404(ProcesVerbal, id=pv_id)
    
    # 2. Création du document Word
    document = Document()

    # Style du titre
    section = document.sections[0]
    header = document.add_heading('PROCÈS-VERBAL DE CONTRÔLE TECHNIQUE', 0)
    header.alignment = WD_ALIGN_PARAGRAPH.CENTER

    # 3. Informations Générales
    document.add_heading('1. Informations Générales', level=1)
    table_info = document.add_table(rows=4, cols=2)
    table_info.style = 'Table Grid'
    
    data_info = [
        ('Type de PV', pv.get_type_pv_display()),
        ('Client', str(pv.client)),
        ('Date de visite', pv.date_visite.strftime('%d/%m/%Y')),
        ('Lieu', pv.lieu or "Non spécifié"),
    ]

    for i, (label, value) in enumerate(data_info):
        table_info.rows[i].cells[0].text = label
        table_info.rows[i].cells[1].text = value

    document.add_paragraph()

    # 4. Points de Contrôle
    document.add_heading('2. Points de Contrôle', level=1)
    controles = pv.controles.all() # Relation inverse ForeignKey
    
    if controles:
        table_ctrl = document.add_table(rows=1, cols=3)
        table_ctrl.style = 'Table Grid'
        hdr_cells = table_ctrl.rows[0].cells
        hdr_cells[0].text = 'Élément'
        hdr_cells[1].text = 'État'
        hdr_cells[2].text = 'Observations'

        for ctrl in controles:
            row_cells = table_ctrl.add_row().cells
            row_cells[0].text = str(ctrl.element)
            row_cells[1].text = ctrl.get_etat_display()
            row_cells[2].text = ctrl.commentaire or ""
    else:
        document.add_paragraph("Aucun point de contrôle enregistré.")

    document.add_paragraph()

    # 5. Participants et Signatures
    document.add_heading('3. Émargements', level=1)
    participants = pv.participants.all()
    
    table_sign = document.add_table(rows=0, cols=2)
    table_sign.style = 'Table Grid'

    for p in participants:
        row = table_sign.add_row().cells
        row[0].text = f"Nom : {p.participant.get_full_name()}"
        
        # Gestion de la signature (si stockée en Base64 ou ImageField)
        if p.signature:
            try:
                # Si c'est du Base64 (venant du SignaturePad)
                if ";base64," in p.signature:
                    format, imgstr = p.signature.split(';base64,')
                    img_data = base64.b64decode(imgstr)
                    img_io = io.BytesIO(img_data)
                    
                    paragraph = row[1].paragraphs[0]
                    run = paragraph.add_run()
                    run.add_picture(img_io, width=Inches(1.5))
            except Exception as e:
                row[1].text = "[Signature non disponible]"

    # 6. Préparation de la réponse HTTP
    buffer = io.BytesIO()
    document.save(buffer)
    buffer.seek(0)

    filename = f"PV_{pv.id}_{pv.date_visite.strftime('%Y%m%d')}.docx"
    response = HttpResponse(
        buffer.getvalue(),
        content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    )
    response['Content-Disposition'] = f'attachment; filename="{filename}"'

    return response

def finaliser_pv(request, pv_id):
    pv = get_object_or_404(ProcesVerbal, id=pv_id)
    
    if request.method == 'POST':
        # Vérifier que le PV n'est pas déjà finalisé
        if pv.est_finalise:
            messages.error(request, 'Ce PV est déjà finalisé.')
            return redirect('controle:detail_pv', pv_id=pv.id)
        
        # Vérifier que tous les participants ont signé
        if pv.participants.filter(a_signe=False).exists():
            messages.error(request, 'Tous les participants doivent signer avant de finaliser le PV.')
            return redirect('controle:detail_pv', pv_id=pv.id)
        
        # Finaliser le PV
        pv.est_finalise = True
        pv.save()
        
        messages.success(request, 'Le PV a été finalisé avec succès.')
    
    return redirect('controle:detail_pv', pv_id=pv.id)
    

# =======================
# Gestion des ouvrages
# =======================

def liste_ouvrages(request):
    ouvrages = Ouvrage.objects.all().order_by('code')

    context = {
        'ouvrages': ouvrages,
    }

    return render(request, 'parametres/liste_ouvrages.html', context)


def ajouter_ouvrage(request):
    if request.method == 'POST':
        code = request.POST.get('code')
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        # Validation
        if not code or not nom:
            messages.error(request, 'Le code et le nom sont obligatoires')
            return redirect('controle:liste_ouvrages')
        
        # Vérifier si le code existe déjà
        if Ouvrage.objects.filter(code=code).exists():
            messages.error(request, f'Un ouvrage avec le code "{code}" existe déjà')
            return redirect('controle:liste_ouvrages')
        
        try:
            Ouvrage.objects.create(
                code=code,
                nom=nom,
                description=description
            )
            messages.success(request, f'Ouvrage "{nom}" ajouté avec succès')
        except Exception as e:
            messages.error(request, f'Erreur lors de l\'ajout : {str(e)}')
    
    return redirect('controle:liste_ouvrages')

def modifier_ouvrage(request):
    if request.method == 'POST':
        ouvrage_id = request.GET.get('id')
        
        if not ouvrage_id:
            messages.error(request, 'ID de l\'ouvrage non spécifié')
            return redirect('controle:liste_ouvrages')
        
        ouvrage = get_object_or_404(Ouvrage, id=ouvrage_id)
        
        code = request.POST.get('code')
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        # Validation
        if not code or not nom:
            messages.error(request, 'Le code et le nom sont obligatoires')
            return redirect('controle:liste_ouvrages')
        
        # Vérifier si le code existe déjà (sauf pour cet ouvrage)
        if Ouvrage.objects.filter(code=code).exclude(id=ouvrage_id).exists():
            messages.error(request, f'Un autre ouvrage avec le code "{code}" existe déjà')
            return redirect('controle:liste_ouvrages')
        
        try:
            ouvrage.code = code
            ouvrage.nom = nom
            ouvrage.description = description
            ouvrage.save()
            messages.success(request, f'Ouvrage "{nom}" modifié avec succès')
        except Exception as e:
            messages.error(request, f'Erreur lors de la modification : {str(e)}')
    
    return redirect('controle:liste_ouvrages')

def supprimer_ouvrage(request):
    if request.method == 'POST':
        ouvrage_id = request.POST.get('id')
        
        if not ouvrage_id:
            messages.error(request, 'ID de l\'ouvrage non spécifié')
            return redirect('controle:liste_ouvrages')
        
        try:
            ouvrage = get_object_or_404(Ouvrage, id=ouvrage_id)
            nom = ouvrage.nom
            ouvrage.delete()
            messages.success(request, f'Ouvrage "{nom}" supprimé avec succès')
        except Exception as e:
            messages.error(request, f'Erreur lors de la suppression : {str(e)}')
    
    return redirect('controle:liste_ouvrages')
 

# ========================
# Gestion des éléments d'ouvrage
# ========================

# Constantes pour les choix d'état
# ETAT_CHOICES = [
#    ('CONFORME', 'Conforme'),
#    ('NON_CONFORME', 'Non conforme'),
 #   ('SUSPENDU', 'Suspendu'),
#]

@login_required
def liste_elements(request):
    """
    Vue pour afficher la liste des éléments d'ouvrages avec filtres et pagination
    """
    # Récupérer tous les ouvrages pour le filtre
    ouvrages = Ouvrage.objects.all().order_by('code')
    
    # Récupérer tous les éléments avec leurs ouvrages associés
    elements = ElementOuvrage.objects.select_related('ouvrage').all()
    
    # Application des filtres
    ouvrage_id = request.GET.get('ouvrage')
   
    
    if ouvrage_id:
        elements = elements.filter(ouvrage_id=ouvrage_id)
    
    
    
    # Recherche textuelle
    search_query = request.GET.get('search')
    if search_query:
        elements = elements.filter(
            Q(code__icontains=search_query) |
            Q(nom__icontains=search_query) |
            Q(description__icontains=search_query) |
            Q(ouvrage__nom__icontains=search_query) |
            Q(ouvrage__code__icontains=search_query)
        )
    
    # Tri - CORRECTION ICI
    tri = request.GET.get('tri', 'ouvrage__code')
    if tri == 'ouvrage__code,code':
        tri = 'ouvrage__code'
    
    # Vérifier que le champ de tri existe
    champs_autorises = ['code', 'nom', 'ouvrage__code', 'ouvrage__nom', '-code', '-nom', '-ouvrage__code', '-ouvrage__nom']
    if tri in champs_autorises:
        elements = elements.order_by(tri)
    else:
        elements = elements.order_by('ouvrage__code', 'code')
    
    # Pagination
    page_number = request.GET.get('page', 1)
    paginator = Paginator(elements, 15)  # 15 éléments par page
    
    try:
        elements_page = paginator.page(page_number)
    except:
        elements_page = paginator.page(1)
    
    context = {
        'elements': elements_page,
        'ouvrages': ouvrages,
        'paginator': paginator,
    }
    
    return render(request, 'parametres/liste_elements.html', context)


@login_required
def ajouter_element(request):
    """
    Vue pour ajouter un nouvel élément d'ouvrage
    """
    if request.method == 'POST':
        # Récupération des données du formulaire
        ouvrage_id = request.POST.get('ouvrage_id')
        code = request.POST.get('code')
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        # Validation des champs requis
        if not ouvrage_id or not code or not nom:
            messages.error(request, "Veuillez remplir tous les champs obligatoires.")
            return redirect('controle:liste_elements')
        
        # Vérifier si l'ouvrage existe
        try:
            ouvrage = Ouvrage.objects.get(id=ouvrage_id)
        except Ouvrage.DoesNotExist:
            messages.error(request, "L'ouvrage sélectionné n'existe pas.")
            return redirect('controle:liste_elements')
        
        # Vérifier l'unicité du code pour cet ouvrage
        if ElementOuvrage.objects.filter(ouvrage_id=ouvrage_id, code=code).exists():
            messages.error(request, f"Un élément avec le code '{code}' existe déjà pour cet ouvrage.")
            return redirect('controle:liste_elements')
        
        # Création de l'élément
        try:
            element = ElementOuvrage.objects.create(
                ouvrage=ouvrage,
                code=code.upper(),  # Mettre en majuscules
                nom=nom,
                description=description
            )
            messages.success(request, f"L'élément '{element.nom}' a été ajouté avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la création : {str(e)}")
        
        return redirect('controle:liste_elements')
    
    # Si méthode GET, rediriger vers la liste
    return redirect('controle:liste_elements')


@login_required
def modifier_element(request, element_id):
    """
    Vue pour modifier un élément d'ouvrage existant
    """
    # Récupérer l'élément à modifier
    element = get_object_or_404(ElementOuvrage, id=element_id)
    
    if request.method == 'POST':
        # Récupération des données du formulaire
        ouvrage_id = request.POST.get('ouvrage_id')
        code = request.POST.get('code')
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        # Validation des champs requis
        if not ouvrage_id or not code or not nom:
            messages.error(request, "Veuillez remplir tous les champs obligatoires.")
            return redirect('controle:liste_elements')
        
        # Vérifier si le nouvel ouvrage existe
        try:
            ouvrage = Ouvrage.objects.get(id=ouvrage_id)
        except Ouvrage.DoesNotExist:
            messages.error(request, "L'ouvrage sélectionné n'existe pas.")
            return redirect('controle:liste_elements')
        
        # Vérifier l'unicité du code (si modifié)
        if element.code != code or element.ouvrage_id != int(ouvrage_id):
            if ElementOuvrage.objects.filter(ouvrage_id=ouvrage_id, code=code).exclude(id=element_id).exists():
                messages.error(request, f"Un élément avec le code '{code}' existe déjà pour cet ouvrage.")
                return redirect('controle:liste_elements')
        
        # Mise à jour de l'élément
        try:
            element.ouvrage = ouvrage
            element.code = code.upper()
            element.nom = nom
            element.description = description
            element.save()
            
            messages.success(request, f"L'élément '{element.nom}' a été modifié avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la modification : {str(e)}")
        
        return redirect('controle:liste_elements')
    
    # Si méthode GET, rediriger vers la liste
    return redirect('controle:liste_elements')


@login_required
def supprimer_element(request, element_id):
    """
    Vue pour supprimer un élément d'ouvrage
    """
    # Récupérer l'élément à supprimer
    element = get_object_or_404(ElementOuvrage, id=element_id)
    
    if request.method == 'POST':
        try:
            nom_element = element.nom
            element.delete()
            messages.success(request, f"L'élément '{nom_element}' a été supprimé avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la suppression : {str(e)}")
        
        return redirect('controle:liste_elements')
    
    # Si méthode GET, rediriger vers la liste
    return redirect('controle:liste_elements')


@login_required
def details_element(request, element_id):
    """
    Vue pour afficher les détails d'un élément d'ouvrage
    """
    # Récupérer l'élément avec ses relations
    element = get_object_or_404(
        ElementOuvrage.objects.select_related('ouvrage'), 
        id=element_id
    )
    
    # Récupérer les ouvrages pour le modal de modification
    ouvrages = Ouvrage.objects.all().order_by('code')
    
    context = {
        'element': element,
        'ouvrages': ouvrages,
    }
    
    return render(request, 'details_element.html', context)

# ==================
# Points de contrôle
# ==================
@login_required
def liste_points_controle(request):
    """
    Vue pour afficher la liste des points de contrôle avec filtres et pagination
    """
    # Récupérer tous les ouvrages pour le filtre (si nécessaire)
    ouvrages = Ouvrage.objects.all().order_by('code')
    
    # Récupérer tous les éléments d'ouvrage pour les filtres et modals
    elements_ouvrage = ElementOuvrage.objects.select_related('ouvrage').all().order_by('ouvrage__code', 'code')
    
    # Récupérer tous les points de contrôle avec leurs relations
    points_controle = PointControle.objects.select_related('element__ouvrage').all().order_by('element__ouvrage__code', 'element__code')
    
    # Application des filtres
    element_id = request.GET.get('element')  # Filtrer par élément d'ouvrage
    
    if element_id:
        points_controle = points_controle.filter(element_id=element_id)
    
    # Pagination (optionnelle)
    paginator = Paginator(points_controle, 15)  # 15 points par page
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'ouvrages': ouvrages,
        'elements_ouvrage': elements_ouvrage,  # Note: pas d'espace dans le nom
        'points_controle': page_obj,  # Utiliser page_obj au lieu de points_controle pour la pagination
    }
    
    return render(request, 'parametres/liste_points_controle.html', context)

@login_required
def ajouter_point_controle(request):
    if request.method == 'POST':
        element_id = request.POST.get('element_id')       
        point_de_controle = request.POST.get('point_de_controle', '')
        
        if not element_id :
            messages.error(request, "Veuillez remplir tous les champs obligatoires.")
            return redirect('controle:liste_points_controle')
        
        try:
            element = ElementOuvrage.objects.get(id=element_id)
            PointControle.objects.create(
                element=element,
                point_de_controle=point_de_controle
            )
            messages.success(request, "Point de contrôle ajouté avec succès.")
        except ElementOuvrage.DoesNotExist:
            messages.error(request, "L'élément sélectionné n'existe pas.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la création : {str(e)}")
        
        return redirect('controle:liste_points_controle')
    
    return redirect('controle:liste_points_controle')

@login_required
def supprimer_point_controle(request, point_id):
    point = get_object_or_404(PointControle, id=point_id)
    
    if request.method == 'POST':
        try:
            element = ElementOuvrage.objects.get(id=point_id)
            point.delete()
            messages.success(request, f"Le point de contrôle '{point.point_de_controle}' a été supprimé avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la suppression : {str(e)}")
        
        return redirect('controle:liste_points_controle')
    
    return redirect('controle:liste_points_controle')

@login_required
def details_point_controle(request, point_id):
    point = get_object_or_404(PointControle.objects.select_related('element__ouvrage'), id=point_id)
    
    context = {
        'point': point,
    }
    
    return render(request, 'details_point_controle.html', context)

@login_required
def modifier_point_controle(request, point_id):
    point = get_object_or_404(PointControle, id=point_id)
    
    if request.method == 'POST':
        element_id = request.POST.get('element_id')        
        point_de_controle = request.POST.get('point_de_controle', '')
        
        if not element_id :
            messages.error(request, "Veuillez remplir tous les champs obligatoires.")
            return redirect(f'{request.path}')
        
        try:
            element = ElementOuvrage.objects.get(id=element_id)
            point.element = element
            point.point_de_controle = point_de_controle
            point.save()
            
            messages.success(request, "Point de contrôle modifié avec succès.")
        except ElementOuvrage.DoesNotExist:
            messages.error(request, "L'élément sélectionné n'existe pas.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la modification : {str(e)}")
        
        return redirect(f'{request.path}')
    
    # Si méthode GET, rediriger vers les détails
    return redirect(f'{request.path}')

# ===================
# Clients et contacts
# ===================
@login_required
def liste_clients(request):
    clients = Client.objects.all().order_by('nom')
    
    context = {
        'clients': clients,
    }
    
    return render(request, 'parametres/liste_clients.html', context)    

def details_client(request, client_id):
    client = get_object_or_404(Client, id=client_id)
    
    context = {
        'client': client,
    }
    
    return render(request, 'details_client.html', context)

def supprimer_client(request, client_id):
    client = get_object_or_404(Client, id=client_id)
    
    if request.method == 'POST':
        try:
            nom_client = client.nom
            client.delete()
            messages.success(request, f"Le client '{nom_client}' a été supprimé avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la suppression : {str(e)}")
        
        return redirect('controle:liste_clients')
    
    return redirect('controle:liste_clients')

def modifier_client(request, client_id):
    client = get_object_or_404(Client, id=client_id)
    
    if request.method == 'POST':
        nom = request.POST.get('nom')
        adresse = request.POST.get('adresse', '')
        telephone = request.POST.get('telephone', '')
        email = request.POST.get('email', '')
        
        if not nom:
            messages.error(request, "Le nom du client est obligatoire.")
            return redirect(f'{request.path}')
        
        try:
            client.nom = nom
            client.adresse = adresse
            client.telephone = telephone
            client.email = email
            client.save()
            
            messages.success(request, "Client modifié avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la modification : {str(e)}")
        
        return redirect('controle:liste_clients')
    
    # Si méthode GET, rediriger vers les détails
    return redirect('controle:liste_clients')

def ajouter_client(request):
    if request.method == 'POST':
        nom = request.POST.get('nom')
        adresse = request.POST.get('adresse', '')
        telephone = request.POST.get('telephone', '')
        email = request.POST.get('email', '')
        
        if not nom:
            messages.error(request, "Le nom du client est obligatoire.")
            return redirect('controle:liste_clients')
        
        try:
            Client.objects.create(
                nom=nom,
                adresse=adresse,
                telephone=telephone,
                email=email
            )
            messages.success(request, f"Client '{nom}' ajouté avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la création : {str(e)}")
        
        return redirect('controle:liste_clients')
    
    # Si méthode GET, rediriger vers la liste
    return redirect('controle:liste_clients')

@login_required
def liste_pv_client(request, client_id):
    client = get_object_or_404(Client, id=client_id)
    pvs = client.procesverbal_set.all().order_by('-date_visite')

    # statistiques simples utilisées par le template
    current_year = timezone.now().year
    stats = {
        'pvs_finaux': pvs.filter(statut='final').count(),
        'pvs_brouillon': pvs.exclude(statut='final').count(),
        'pvs_annee': pvs.filter(date_visite__year=current_year).count(),
    }
    dernier_pv = pvs.first()  # déjà trié par date_visite desc
    types_pv = TypePV.objects.all()

    context = {
        'client': client,
        'pvs': pvs,
        'stats': stats,
        'dernier_pv': dernier_pv,
        'types_pv': types_pv,
    }

    return render(request, 'liste_pv_client.html', context)

# ===================

def liste_projets(request):
    projets = Projets.objects.all().order_by('nom')
    
    context = {
        'projets': projets,
    }
    
    return render(request, 'parametres/liste_projets.html', context)

def details_projet(request, projet_id):
    projet = get_object_or_404(Projets, id=projet_id)
    
    context = {
        'projet': projet,
    }
    
    return render(request, 'details_projet.html', context)

def supprimer_projet(request, projet_id):
    projet = get_object_or_404(Projets, id=projet_id)
    
    if request.method == 'POST':
        try:
            nom_projet = projet.nom
            projet.delete()
            messages.success(request, f"Le projet '{nom_projet}' a été supprimé avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la suppression : {str(e)}")
        
        return redirect('liste_projets')
    
    return redirect('liste_projets')    

def modifier_projet(request, projet_id):
    projet = get_object_or_404(Projets, id=projet_id)
    
    if request.method == 'POST':
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        if not nom:
            messages.error(request, "Le nom du projet est obligatoire.")
            return redirect(f'{request.path}')
        
        try:
            projet.nom = nom
            projet.description = description
            projet.save()
            
            messages.success(request, "Projet modifié avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la modification : {str(e)}")
        
        return redirect(f'{request.path}')
    
    # Si méthode GET, rediriger vers les détails
    return redirect(f'{request.path}')  

def ajouter_projet(request):
    if request.method == 'POST':
        nom = request.POST.get('nom')
        description = request.POST.get('description', '')
        
        if not nom:
            messages.error(request, "Le nom du projet est obligatoire.")
            return redirect('liste_projets')
        
        try:
            Projets.objects.create(
                nom=nom,
                description=description
            )
            messages.success(request, f"Projet '{nom}' ajouté avec succès.")
        except Exception as e:
            messages.error(request, f"Erreur lors de la création : {str(e)}")
        
        return redirect('liste_projets')
    
    # Si méthode GET, rediriger vers la liste
    return redirect('liste_projets')

# ===================
# Exportateurs et API
# ===================

@login_required
def exporter_elements(request):
    """
    Vue pour exporter la liste des éléments (CSV)
    """
    import csv
    from django.http import HttpResponse
    import datetime
    
    # Récupérer les éléments avec filtres si nécessaire
    elements = ElementOuvrage.objects.select_related('ouvrage').all()
    
    # Créer la réponse HTTP avec en-tête CSV
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = f'attachment; filename="elements_ouvrages_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.csv"'
    
    # Créer le writer CSV
    writer = csv.writer(response)
    writer.writerow(['Ouvrage', 'Code Ouvrage', 'Code Élément', 'Nom', 'Description'])
    
    # Écrire les données
    for element in elements:
        writer.writerow([
            element.ouvrage.nom,
            element.ouvrage.code,
            element.code,
            element.nom,
            element.description,
        ])
    
    messages.success(request, "Export effectué avec succès.")
    return response


@login_required
def elements_par_ouvrage(request, ouvrage_id):
    """
    Vue API pour récupérer les éléments d'un ouvrage spécifique
    """
    from django.http import JsonResponse
    
    # Récupérer les éléments de l'ouvrage
    elements = ElementOuvrage.objects.filter(ouvrage_id=ouvrage_id).values('id', 'code', 'nom')
    
    return JsonResponse(list(elements), safe=False)
        

  