/**
 * EM2RP Cloud Functions
 * Architecture backend sécurisée avec authentification et permissions
 */

// Charger les variables d'environnement depuis .env
require('dotenv').config();

const { onRequest, onCall } = require("firebase-functions/v2/https");
const { onSchedule } = require("firebase-functions/v2/scheduler");
const { onDocumentCreated, onDocumentUpdated } = require("firebase-functions/v2/firestore");
const logger = require("firebase-functions/logger");
const admin = require('firebase-admin');
const { Storage } = require('@google-cloud/storage');

// Utilitaires
const auth = require('./utils/auth');
const helpers = require('./utils/helpers');
const { generateTTS } = require('./generateTTS');

// Initialisation sécurisée
if (!admin.apps.length) {
  admin.initializeApp();
}
const storage = new Storage();
const db = admin.firestore();

// Configuration commune pour toutes les fonctions HTTP
const httpOptions = {
  cors: false,
  invoker: 'public', // Permet les invocations non authentifiées (l'auth est gérée par notre token Firebase)
  region: 'europe-west9', // Région européenne (Paris)
  // Version: 2.0 - Ajout de l'invoker public pour résoudre les problèmes CORS
};

// ============================================================================
// CORS Middleware
// ============================================================================
const setCorsHeaders = (res, req) => {
  // Utiliser l'origin de la requête pour permettre les credentials
  const origin = req.headers.origin || '*';

  res.set('Access-Control-Allow-Origin', origin);

  // N'autoriser les credentials que si on a un origin spécifique (pas '*')
  if (origin !== '*') {
    res.set('Access-Control-Allow-Credentials', 'true');
  }

  res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.set('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept, Origin, X-Requested-With');
  res.set('Access-Control-Max-Age', '3600');
};

// Wrapper pour les fonctions avec CORS
const withCors = (handler) => {
  return async (req, res) => {
    // Définir les headers CORS pour toutes les requêtes
    setCorsHeaders(res, req);

    // Gérer les requêtes preflight OPTIONS immédiatement
    if (req.method === 'OPTIONS') {
      res.status(204).send('');
      return;
    }

    // Exécuter le handler
    try {
      await handler(req, res);
    } catch (error) {
      logger.error("Unhandled error:", error);
      if (!res.headersSent) {
        res.status(500).json({ error: error.message });
      }
    }
  };
};

// ============================================================================
// STORAGE - Move Event File
// ============================================================================
exports.moveEventFileV2 = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { sourcePath, destinationPath } = req.body.data || {};

    if (!sourcePath || !destinationPath) {
      res.status(400).json({ error: 'Source and destination paths are required.' });
      return;
    }

    const bucketName = admin.storage().bucket().name;
    const bucket = storage.bucket(bucketName);

    await bucket.file(sourcePath).copy(bucket.file(destinationPath));
    await bucket.file(sourcePath).delete();
    const [url] = await bucket.file(destinationPath).getSignedUrl({
      action: 'read',
      expires: '03-01-2500',
    });

    res.status(200).json({ url });
  } catch (error) {
    logger.error("Error moving file:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EQUIPMENT - CRUD
// ============================================================================

// Créer un équipement (admin ou manage_equipment)
exports.createEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const equipmentData = req.body.data;
    const equipmentId = equipmentData.id;

    if (!equipmentId) {
      res.status(400).json({ error: 'Equipment ID is required' });
      return;
    }

    // Vérifier unicité de l'ID
    const existingDoc = await db.collection('equipments').doc(equipmentId).get();
    if (existingDoc.exists) {
      res.status(409).json({ error: 'Equipment ID already exists' });
      return;
    }

    // Convertir les timestamps
    const dataToSave = helpers.deserializeTimestamps(equipmentData, [
      'createdAt', 'updatedAt', 'purchaseDate', 'lastMaintenanceDate', 'nextMaintenanceDate'
    ]);

    await db.collection('equipments').doc(equipmentId).set(dataToSave);

    res.status(201).json({ id: equipmentId, message: 'Equipment created successfully' });
  } catch (error) {
    logger.error("Error creating equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour un équipement
exports.updateEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { equipmentId, data } = req.body.data;

    if (!equipmentId) {
      res.status(400).json({ error: 'Equipment ID is required' });
      return;
    }

    if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {
      res.status(400).json({ error: 'Update data is required and must be a non-empty object' });
      return;
    }

    // Empêcher la modification de l'ID
    delete data.id;

    // Ajouter updatedAt
    data.updatedAt = admin.firestore.Timestamp.now();

    const dataToSave = helpers.deserializeTimestamps(data, [
      'purchaseDate', 'lastMaintenanceDate', 'nextMaintenanceDate'
    ]);

    await db.collection('equipments').doc(equipmentId).update(dataToSave);

    res.status(200).json({ message: 'Equipment updated successfully' });
  } catch (error) {
    logger.error("Error updating equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer un équipement
exports.deleteEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { equipmentId } = req.body.data;

    if (!equipmentId) {
      res.status(400).json({ error: 'Equipment ID is required' });
      return;
    }

    // Vérifier si l'équipement est utilisé dans des événements actifs
    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    for (const eventDoc of eventsSnapshot.docs) {
      const eventData = eventDoc.data();
      const assignedEquipment = eventData.assignedEquipment || [];

      if (assignedEquipment.some(eq => eq.equipmentId === equipmentId)) {
        res.status(409).json({
          error: 'Cannot delete equipment: it is assigned to active events',
          eventId: eventDoc.id
        });
        return;
      }
    }

    await db.collection('equipments').doc(equipmentId).delete();

    res.status(200).json({ message: 'Equipment deleted successfully' });
  } catch (error) {
    logger.error("Error deleting equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Récupérer un équipement par ID
exports.getEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasViewAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
    const hasManageAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasViewAccess && !hasManageAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentId } = req.body.data || req.query;

    if (!equipmentId) {
      res.status(400).json({ error: 'Equipment ID is required' });
      return;
    }

    const doc = await db.collection('equipments').doc(equipmentId).get();

    if (!doc.exists) {
      res.status(404).json({ error: 'Equipment not found' });
      return;
    }

    let data = { id: doc.id, ...doc.data() };
    data = helpers.serializeTimestamps(data);
    data = helpers.serializeReferences(data);

    // Masquer les prix si pas de permission manage_equipment
    data = helpers.maskSensitiveFields(data, hasManageAccess);

    res.status(200).json({ equipment: data });
  } catch (error) {
    logger.error("Error getting equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Récupérer plusieurs équipements par leurs IDs
exports.getEquipmentsByIds = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasViewAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
    const hasManageAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasViewAccess && !hasManageAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentIds } = req.body.data || {};

    if (!equipmentIds || !Array.isArray(equipmentIds) || equipmentIds.length === 0) {
      res.status(400).json({ error: 'equipmentIds array is required and must not be empty' });
      return;
    }

    // Limiter à 100 équipements max par requête
    if (equipmentIds.length > 100) {
      res.status(400).json({ error: 'Maximum 100 equipment IDs per request' });
      return;
    }

    // Récupérer tous les documents en parallèle
    const promises = equipmentIds.map(id => db.collection('equipments').doc(id).get());
    const docs = await Promise.all(promises);

    const equipments = [];
    for (const doc of docs) {
      if (doc.exists) {
        let data = { id: doc.id, ...doc.data() };
        data = helpers.serializeTimestamps(data);
        data = helpers.serializeReferences(data);
        // Masquer les prix si pas de permission manage_equipment
        data = helpers.maskSensitiveFields(data, hasManageAccess);
        equipments.push(data);
      }
    }

    res.status(200).json({ equipments });
  } catch (error) {
    logger.error("Error getting equipments by IDs:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// CONTAINERS - CRUD
// ============================================================================

// Créer un container
exports.createContainer = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const containerData = req.body.data;
    const containerId = containerData.id;

    if (!containerId) {
      res.status(400).json({ error: 'Container ID is required' });
      return;
    }

    const existingDoc = await db.collection('containers').doc(containerId).get();
    if (existingDoc.exists) {
      res.status(409).json({ error: 'Container ID already exists' });
      return;
    }

    const dataToSave = helpers.deserializeTimestamps(containerData, ['createdAt', 'updatedAt']);

    await db.collection('containers').doc(containerId).set(dataToSave);

    res.status(201).json({ id: containerId, message: 'Container created successfully' });
  } catch (error) {
    logger.error("Error creating container:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour un container
exports.updateContainer = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { containerId, data } = req.body.data;

    if (!containerId) {
      res.status(400).json({ error: 'Container ID is required' });
      return;
    }

    delete data.id;
    data.updatedAt = admin.firestore.Timestamp.now();

    await db.collection('containers').doc(containerId).update(data);

    res.status(200).json({ message: 'Container updated successfully' });
  } catch (error) {
    logger.error("Error updating container:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer un container
exports.deleteContainer = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { containerId } = req.body.data;

    if (!containerId) {
      res.status(400).json({ error: 'Container ID is required' });
      return;
    }

    // Récupérer le container pour obtenir les équipements
    const containerDoc = await db.collection('containers').doc(containerId).get();
    if (containerDoc.exists) {
      const containerData = containerDoc.data();
      const equipmentIds = containerData.equipmentIds || [];

      // Retirer le container des parentBoxIds de chaque équipement
      for (const equipmentId of equipmentIds) {
        try {
          const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
          if (equipmentDoc.exists) {
            const equipmentData = equipmentDoc.data();
            const parentBoxIds = (equipmentData.parentBoxIds || []).filter(boxId => boxId !== containerId);
            await db.collection('equipments').doc(equipmentId).update({
              parentBoxIds: parentBoxIds,
              updatedAt: admin.firestore.Timestamp.now(),
            });
          }
        } catch (err) {
          logger.error(`Error updating equipment ${equipmentId} when deleting container:`, err);
        }
      }
    }

    await db.collection('containers').doc(containerId).delete();

    res.status(200).json({ message: 'Container deleted successfully' });
  } catch (error) {
    logger.error("Error deleting container:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Récupérer les containers contenant un équipement
exports.getContainersByEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasViewAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
    const hasManageAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasViewAccess && !hasManageAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentId } = req.body.data || {};

    if (!equipmentId) {
      res.status(400).json({ error: 'equipmentId is required' });
      return;
    }

    const snapshot = await db.collection('containers')
      .where('equipmentIds', 'array-contains', equipmentId)
      .get();

    const containers = [];
    snapshot.forEach(doc => {
      let data = { id: doc.id, ...doc.data() };
      data = helpers.serializeTimestamps(data);
      data = helpers.serializeReferences(data);
      containers.push(data);
    });

    res.status(200).json({ containers });
  } catch (error) {
    logger.error("Error getting containers by equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Récupérer plusieurs containers par leurs IDs
exports.getContainersByIds = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasViewAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
    const hasManageAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasViewAccess && !hasManageAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { containerIds } = req.body.data || {};

    if (!containerIds || !Array.isArray(containerIds) || containerIds.length === 0) {
      res.status(400).json({ error: 'containerIds array is required and must not be empty' });
      return;
    }

    // Limiter à 100 conteneurs max par requête
    if (containerIds.length > 100) {
      res.status(400).json({ error: 'Maximum 100 container IDs per request' });
      return;
    }

    // Récupérer tous les documents en parallèle
    const promises = containerIds.map(id => db.collection('containers').doc(id).get());
    const docs = await Promise.all(promises);

    const containers = [];
    for (const doc of docs) {
      if (doc.exists) {
        let data = { id: doc.id, ...doc.data() };
        data = helpers.serializeTimestamps(data);
        data = helpers.serializeReferences(data);
        containers.push(data);
      }
    }

    res.status(200).json({ containers });
  } catch (error) {
    logger.error("Error getting containers by IDs:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Ajouter un équipement à un container
 */
exports.addEquipmentToContainer = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { containerId, equipmentId, userId } = req.body.data;

    if (!containerId || !equipmentId) {
      res.status(400).json({ error: 'containerId and equipmentId are required' });
      return;
    }

    // Récupérer le container
    const containerDoc = await db.collection('containers').doc(containerId).get();
    if (!containerDoc.exists) {
      res.status(404).json({ success: false, message: 'Container non trouvé' });
      return;
    }

    const containerData = containerDoc.data();
    const equipmentIds = containerData.equipmentIds || [];

    // Vérifier si l'équipement n'est pas déjà dans ce container
    if (equipmentIds.includes(equipmentId)) {
      res.status(400).json({ success: false, message: 'Cet équipement est déjà dans ce container' });
      return;
    }

    // Récupérer l'équipement
    const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
    if (!equipmentDoc.exists) {
      res.status(404).json({ success: false, message: 'Équipement non trouvé' });
      return;
    }

    const equipmentData = equipmentDoc.data();
    const parentBoxIds = equipmentData.parentBoxIds || [];

    // Vérifier les autres containers
    const warnings = [];
    if (parentBoxIds.length > 0) {
      const otherContainersPromises = parentBoxIds.map(boxId =>
        db.collection('containers').doc(boxId).get()
      );
      const otherContainersDocs = await Promise.all(otherContainersPromises);
      const otherNames = otherContainersDocs
        .filter(doc => doc.exists)
        .map(doc => doc.data().name);

      if (otherNames.length > 0) {
        warnings.push(`Attention : cet équipement est également dans les boites suivants : ${otherNames.join(", ")}`);
      }
    }

    // Mettre à jour le container
    await db.collection('containers').doc(containerId).update({
      equipmentIds: [...equipmentIds, equipmentId],
      updatedAt: admin.firestore.Timestamp.now(),
    });

    // Mettre à jour l'équipement
    await db.collection('equipments').doc(equipmentId).update({
      parentBoxIds: [...parentBoxIds, containerId],
      updatedAt: admin.firestore.Timestamp.now(),
    });

    // Ajouter une entrée dans l'historique
    const history = containerData.history || [];
    const historyEntry = {
      timestamp: admin.firestore.Timestamp.now(),
      action: 'equipment_added',
      equipmentId: equipmentId,
      newValue: equipmentId,
      userId: userId || decodedToken.uid,
    };

    const updatedHistory = [...history, historyEntry].slice(-100); // Garder les 100 dernières entrées

    await db.collection('containers').doc(containerId).update({
      history: updatedHistory,
    });

    res.status(200).json({
      success: true,
      message: 'Équipement ajouté avec succès',
      warnings: warnings.length > 0 ? warnings[0] : null,
    });
  } catch (error) {
    logger.error("Error adding equipment to container:", error);
    res.status(500).json({ success: false, message: error.message });
  }
}));

/**
 * Retirer un équipement d'un container
 */
exports.removeEquipmentFromContainer = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { containerId, equipmentId, userId } = req.body.data;

    if (!containerId || !equipmentId) {
      res.status(400).json({ error: 'containerId and equipmentId are required' });
      return;
    }

    // Récupérer le container
    const containerDoc = await db.collection('containers').doc(containerId).get();
    if (!containerDoc.exists) {
      res.status(404).json({ error: 'Container non trouvé' });
      return;
    }

    const containerData = containerDoc.data();
    const equipmentIds = containerData.equipmentIds || [];

    // Retirer l'équipement du container
    const updatedEquipmentIds = equipmentIds.filter(id => id !== equipmentId);

    await db.collection('containers').doc(containerId).update({
      equipmentIds: updatedEquipmentIds,
      updatedAt: admin.firestore.Timestamp.now(),
    });

    // Mettre à jour l'équipement
    const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
    if (equipmentDoc.exists) {
      const equipmentData = equipmentDoc.data();
      const parentBoxIds = equipmentData.parentBoxIds || [];
      const updatedParentBoxIds = parentBoxIds.filter(id => id !== containerId);

      await db.collection('equipments').doc(equipmentId).update({
        parentBoxIds: updatedParentBoxIds,
        updatedAt: admin.firestore.Timestamp.now(),
      });
    }

    // Ajouter une entrée dans l'historique
    const history = containerData.history || [];
    const historyEntry = {
      timestamp: admin.firestore.Timestamp.now(),
      action: 'equipment_removed',
      equipmentId: equipmentId,
      previousValue: equipmentId,
      userId: userId || decodedToken.uid,
    };

    const updatedHistory = [...history, historyEntry].slice(-100);

    await db.collection('containers').doc(containerId).update({
      history: updatedHistory,
    });

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error removing equipment from container:", error);
    res.status(500).json({ error: error.message });
  }
}));


// ============================================================================
// EVENTS - CRUD
// ============================================================================

// Créer un événement
exports.createEvent = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'edit_event');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires edit_event permission' });
      return;
    }

    const eventData = req.body.data;

    // Désérialiser les timestamps
    let dataToSave = helpers.deserializeTimestamps(eventData, [
      'StartDateTime', 'EndDateTime', 'createdAt', 'updatedAt'
    ]);

    // Convertir les IDs en DocumentReference pour compatibilité avec l'ancien format
    dataToSave = helpers.convertIdsToReferences(dataToSave);

    const docRef = await db.collection('events').add(dataToSave);

    res.status(201).json({ id: docRef.id, message: 'Event created successfully' });
  } catch (error) {
    logger.error("Error creating event:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour un événement
exports.updateEvent = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'edit_event');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires edit_event permission' });
      return;
    }

    const requestData = req.body.data;
    logger.info(`Update event - requestData keys: ${Object.keys(requestData || {}).join(', ')}`);

    const eventId = requestData.eventId;
    logger.info(`Update event - eventId: ${eventId}`);

    if (!eventId) {
      logger.error('Event ID is missing from request');
      res.status(400).json({ error: 'Event ID is required' });
      return;
    }

    // Extraire eventId et préparer les données à sauvegarder
    const { eventId: _, ...data } = requestData;

    if (!data || Object.keys(data).length === 0) {
      res.status(400).json({ error: 'No data to update' });
      return;
    }

    delete data.id;
    data.updatedAt = admin.firestore.Timestamp.now();

    // Désérialiser les timestamps
    let dataToSave = helpers.deserializeTimestamps(data, [
      'StartDateTime', 'EndDateTime'
    ]);

    // Convertir les IDs en DocumentReference pour compatibilité avec l'ancien format
    dataToSave = helpers.convertIdsToReferences(dataToSave);

    await db.collection('events').doc(eventId).update(dataToSave);

    res.status(200).json({ message: 'Event updated successfully' });
  } catch (error) {
    logger.error("Error updating event:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer un événement
exports.deleteEvent = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'delete_event');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires delete_event permission' });
      return;
    }

    const { eventId } = req.body.data;

    if (!eventId) {
      res.status(400).json({ error: 'Event ID is required' });
      return;
    }

    await db.collection('events').doc(eventId).delete();

    res.status(200).json({ message: 'Event deleted successfully' });
  } catch (error) {
    logger.error("Error deleting event:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// MAINTENANCES - CRUD
// ============================================================================

// Créer une maintenance
exports.createMaintenance = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_maintenances');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_maintenances permission' });
      return;
    }

    const maintenanceData = req.body.data;

    const dataToSave = helpers.deserializeTimestamps(maintenanceData, [
      'scheduledDate', 'completedDate', 'createdAt', 'updatedAt'
    ]);

    const docRef = await db.collection('maintenances').add(dataToSave);
    const maintenanceId = docRef.id;

    // Mettre à jour les équipements concernés
    if (maintenanceData.equipmentIds && Array.isArray(maintenanceData.equipmentIds)) {
      for (const equipmentId of maintenanceData.equipmentIds) {
        try {
          const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
          if (equipmentDoc.exists) {
            const equipmentData = equipmentDoc.data();
            const maintenanceIds = equipmentData.maintenanceIds || [];
            if (!maintenanceIds.includes(maintenanceId)) {
              maintenanceIds.push(maintenanceId);
              await db.collection('equipments').doc(equipmentId).update({
                maintenanceIds: maintenanceIds,
              });
            }
          }

          // Si la maintenance est planifiée dans les 7 prochains jours, créer une alerte
          if (maintenanceData.scheduledDate) {
            const scheduledDate = maintenanceData.scheduledDate.toDate ?
              maintenanceData.scheduledDate.toDate() :
              new Date(maintenanceData.scheduledDate);
            const sevenDaysFromNow = new Date();
            sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);

            if (scheduledDate <= sevenDaysFromNow) {
              // Vérifier si une alerte existe déjà
              const existingAlerts = await db.collection('alerts')
                .where('equipmentId', '==', equipmentId)
                .where('type', '==', 'maintenanceDue')
                .where('isRead', '==', false)
                .get();

              let alertExists = false;
              for (const alertDoc of existingAlerts.docs) {
                const alertData = alertDoc.data();
                if (alertData.message && alertData.message.includes(maintenanceData.name || '')) {
                  alertExists = true;
                  break;
                }
              }

              if (!alertExists) {
                const equipmentName = equipmentDoc.exists ?
                  (equipmentDoc.data().name || equipmentId) :
                  equipmentId;

                const daysUntil = Math.ceil((scheduledDate - new Date()) / (1000 * 60 * 60 * 24));

                await db.collection('alerts').add({
                  type: 'maintenanceDue',
                  message: `Maintenance "${maintenanceData.name || 'Sans nom'}" prévue dans ${daysUntil} jour(s) pour ${equipmentName}`,
                  equipmentId: equipmentId,
                  createdAt: admin.firestore.Timestamp.now(),
                  isRead: false,
                });
              }
            }
          }
        } catch (err) {
          logger.error(`Error updating equipment ${equipmentId} for maintenance:`, err);
        }
      }
    }

    res.status(201).json({ id: maintenanceId, message: 'Maintenance created successfully' });
  } catch (error) {
    logger.error("Error creating maintenance:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour une maintenance
exports.updateMaintenance = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_maintenances');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_maintenances permission' });
      return;
    }

    const { maintenanceId, data } = req.body.data;

    if (!maintenanceId) {
      res.status(400).json({ error: 'Maintenance ID is required' });
      return;
    }

    delete data.id;
    data.updatedAt = admin.firestore.Timestamp.now();

    const dataToSave = helpers.deserializeTimestamps(data, [
      'scheduledDate', 'completedDate'
    ]);

    await db.collection('maintenances').doc(maintenanceId).update(dataToSave);

    res.status(200).json({ message: 'Maintenance updated successfully' });
  } catch (error) {
    logger.error("Error updating maintenance:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// OPTIONS - CRUD
// ============================================================================

// Créer une option
exports.createOption = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const optionData = req.body.data;
    const optionId = optionData.id;

    if (!optionId) {
      res.status(400).json({ error: 'Option ID is required' });
      return;
    }

    await db.collection('options').doc(optionId).set(optionData);

    res.status(201).json({ id: optionId, message: 'Option created successfully' });
  } catch (error) {
    logger.error("Error creating option:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour une option
exports.updateOption = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const { optionId, data } = req.body.data;

    if (!optionId) {
      res.status(400).json({ error: 'Option ID is required' });
      return;
    }

    delete data.id;

    await db.collection('options').doc(optionId).update(data);

    res.status(200).json({ message: 'Option updated successfully' });
  } catch (error) {
    logger.error("Error updating option:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer une option
exports.deleteOption = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const { optionId } = req.body.data;

    if (!optionId) {
      res.status(400).json({ error: 'Option ID is required' });
      return;
    }

    await db.collection('options').doc(optionId).delete();

    res.status(200).json({ message: 'Option deleted successfully' });
  } catch (error) {
    logger.error("Error deleting option:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// USERS - CRUD
// ============================================================================

// Créer un utilisateur
exports.createUser = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const userData = req.body.data;
    const userId = userData.uid;

    if (!userId) {
      res.status(400).json({ error: 'User ID is required' });
      return;
    }

    await db.collection('users').doc(userId).set(userData);

    res.status(201).json({ id: userId, message: 'User created successfully' });
  } catch (error) {
    logger.error("Error creating user:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Créer un utilisateur avec invitation par email
exports.createUserWithInvite = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const { email, firstName, lastName, phoneNumber, roleId } = req.body.data;

    if (!email || !firstName || !lastName || !roleId) {
      res.status(400).json({ error: 'email, firstName, lastName, and roleId are required' });
      return;
    }

    // Générer un mot de passe temporaire aléatoire
    const tempPassword = Math.random().toString(36).slice(-12) + 'Aa1!';

    // Créer l'utilisateur dans Firebase Auth
    let userRecord;
    try {
      userRecord = await admin.auth().createUser({
        email: email,
        password: tempPassword,
        emailVerified: false,
        displayName: `${firstName} ${lastName}`,
      });
    } catch (authError) {
      logger.error("Error creating user in Auth:", authError);
      res.status(500).json({ error: `Failed to create user in Auth: ${authError.message}` });
      return;
    }

    // Créer le document utilisateur dans Firestore
    try {
      await db.collection('users').doc(userRecord.uid).set({
        firstName: firstName,
        lastName: lastName,
        email: email,
        phoneNumber: phoneNumber || '',
        profilePhotoUrl: '',
        role: db.collection('roles').doc(roleId),
        createdAt: admin.firestore.FieldValue.serverTimestamp(),
        createdBy: decodedToken.uid,
      });
    } catch (firestoreError) {
      // Si la création Firestore échoue, supprimer l'utilisateur Auth
      logger.error("Error creating user in Firestore:", firestoreError);
      try {
        await admin.auth().deleteUser(userRecord.uid);
      } catch (cleanupError) {
        logger.error("Error cleaning up Auth user:", cleanupError);
      }
      res.status(500).json({ error: `Failed to create user in Firestore: ${firestoreError.message}` });
      return;
    }

    // Envoyer l'email de réinitialisation du mot de passe
    // Utilisation de l'API REST de Firebase Auth pour déclencher l'envoi automatique
    try {
      const axios = require('axios');
      const firebaseApiKey = 'AIzaSyARQL4P-t5l-cNjQNP9cMokQrLJ8BorF0U'; // Web API Key

      await axios.post(
        `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${firebaseApiKey}`,
        {
          requestType: 'PASSWORD_RESET',
          email: email,
        }
      );
      logger.info(`Password reset email sent to ${email}`);
    } catch (emailError) {
      logger.warn(`Could not send password reset email to ${email}: ${emailError.message}`);
      // Ne pas faire échouer la requête si l'email ne peut pas être envoyé
    }

    logger.info(`User ${userRecord.uid} created by ${decodedToken.uid}`);
    res.status(201).json({
      id: userRecord.uid,
      message: 'User created successfully. Password reset email sent.',
    });
  } catch (error) {
    logger.error("Error in createUserWithInvite:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour un utilisateur
exports.updateUser = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { userId, data } = req.body.data;

    if (!userId) {
      res.status(400).json({ error: 'User ID is required' });
      return;
    }

    // Vérifier si l'utilisateur met à jour son propre profil ou est admin
    const isOwnProfile = decodedToken.uid === userId;
    const isAdminUser = await auth.isAdmin(decodedToken.uid);
    const hasEditPermission = await auth.hasPermission(decodedToken.uid, 'edit_user');

    if (!isOwnProfile && !isAdminUser && !hasEditPermission) {
      res.status(403).json({ error: 'Forbidden: Cannot edit other users' });
      return;
    }

    // Si mise à jour propre profil, limiter les champs modifiables
    if (isOwnProfile && !isAdminUser) {
    const allowedFields = ['firstName', 'lastName', 'phoneNumber', 'profilePhotoUrl', 'notificationPreferences'];
      const filteredData = {};

      for (const field of allowedFields) {
        if (data[field] !== undefined) {
          filteredData[field] = data[field];
        }
      }

      await db.collection('users').doc(userId).update(filteredData);
    } else {
      delete data.uid;

      // Convertir le role string en DocumentReference si présent
      if (data.role && typeof data.role === 'string') {
        data.role = db.collection('roles').doc(data.role);
      }

      await db.collection('users').doc(userId).update(data);
    }

    res.status(200).json({ message: 'User updated successfully' });
  } catch (error) {
    logger.error("Error updating user:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer un utilisateur
exports.deleteUser = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Admin access required' });
      return;
    }

    const { userId } = req.body.data;

    if (!userId) {
      res.status(400).json({ error: 'User ID is required' });
      return;
    }

    // Empêcher un admin de se supprimer lui-même
    if (decodedToken.uid === userId) {
      res.status(400).json({ error: 'Cannot delete your own account' });
      return;
    }

    // Supprimer le document utilisateur dans Firestore
    await db.collection('users').doc(userId).delete();

    // Optionnel: Supprimer l'utilisateur de Firebase Auth
    // Note: Cela nécessite le SDK Admin et des privilèges élevés
    try {
      await admin.auth().deleteUser(userId);
    } catch (authError) {
      logger.warn(`Could not delete user from Auth: ${authError.message}`);
      // On continue même si la suppression Auth échoue
    }

    res.status(200).json({ message: 'User deleted successfully' });
  } catch (error) {
    logger.error("Error deleting user:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EQUIPMENT STATUS - Batch Update
// ============================================================================

// Mettre à jour le statut de plusieurs équipements (pour préparation/retour)
exports.updateEquipmentStatus = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { eventId, updates } = req.body.data;

    if (!eventId || !updates || !Array.isArray(updates)) {
      res.status(400).json({ error: 'Event ID and updates array are required' });
      return;
    }

    // Vérifier que l'utilisateur est assigné à l'événement ou est admin
    const isAssigned = await auth.isAssignedToEvent(decodedToken.uid, eventId);
    const isAdminUser = await auth.isAdmin(decodedToken.uid);

    if (!isAssigned && !isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Not assigned to this event' });
      return;
    }

    // Batch update
    const batch = db.batch();

    for (const update of updates) {
      const { equipmentId, status } = update;
      if (equipmentId && status) {
        const equipmentRef = db.collection('equipments').doc(equipmentId);
        batch.update(equipmentRef, { status });
      }
    }

    await batch.commit();

    res.status(200).json({ message: 'Equipment statuses updated successfully' });
  } catch (error) {
    logger.error("Error updating equipment statuses:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// OPTIONS - Read (public pour utilisateurs authentifiés)
// ============================================================================
exports.getOptions = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req); // Juste vérifier l'auth

    const snapshot = await db.collection('options').get();
    const options = snapshot.docs.map(doc => ({
      id: doc.id,
      ...helpers.serializeTimestamps(doc.data())
    }));

    res.status(200).json({ options });
  } catch (error) {
    logger.error("Error fetching options:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENT TYPES - Read (public pour utilisateurs authentifiés)
// ============================================================================
exports.getEventTypes = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req); // Juste vérifier l'auth

    const snapshot = await db.collection('eventTypes').get();
    const eventTypes = snapshot.docs.map(doc => ({
      id: doc.id,
      ...helpers.serializeTimestamps(doc.data())
    }));

    res.status(200).json({ eventTypes });
  } catch (error) {
    logger.error("Error fetching event types:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// ROLES - Read (public pour utilisateurs authentifiés)
// ============================================================================
exports.getRoles = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req); // Juste vérifier l'auth

    const snapshot = await db.collection('roles').get();
    const roles = snapshot.docs.map(doc => ({
      id: doc.id,
      ...helpers.serializeTimestamps(doc.data())
    }));

    res.status(200).json({ roles });
  } catch (error) {
    logger.error("Error fetching roles:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENT EQUIPMENT - Update equipment status and quantities
// ============================================================================
exports.updateEventEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { eventId, assignedEquipment, preparationStatus, loadingStatus, unloadingStatus, returnStatus } = req.body.data;

    if (!eventId) {
      res.status(400).json({ error: 'Event ID is required' });
      return;
    }

    // Vérifier les permissions
    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const isAdminUser = await auth.hasPermission(decodedToken.uid, 'edit_event');

    // Vérifier si l'utilisateur est assigné en vérifiant workforce de manière sécurisée
    let isAssigned = false;
    if (eventData.workforce && Array.isArray(eventData.workforce)) {
      isAssigned = eventData.workforce.some(ref => {
        if (!ref || !ref.path) return false;
        return ref.path.endsWith(decodedToken.uid) || ref.path === `/users/${decodedToken.uid}`;
      });
    }

    if (!isAssigned && !isAdminUser) {
      res.status(403).json({ error: 'Forbidden: Not assigned to this event' });
      return;
    }

    // Préparer les données à mettre à jour
    const updateData = {};

    if (assignedEquipment) {
      // Convertir les timestamps dans assignedEquipment
      updateData.assignedEquipment = assignedEquipment.map(eq =>
        helpers.deserializeTimestamps(eq, [])
      );
    }

    if (preparationStatus) updateData.preparationStatus = preparationStatus;
    if (loadingStatus) updateData.loadingStatus = loadingStatus;
    if (unloadingStatus) updateData.unloadingStatus = unloadingStatus;
    if (returnStatus) updateData.returnStatus = returnStatus;

    // Mettre à jour l'événement
    await db.collection('events').doc(eventId).update(updateData);

    res.status(200).json({ message: 'Event equipment updated successfully' });
  } catch (error) {
    logger.error("Error updating event equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EQUIPMENT STATUS - Update individual equipment status
// ============================================================================
exports.updateEquipmentStatusOnly = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { equipmentId, status, availableQuantity } = req.body.data;

    if (!equipmentId) {
      res.status(400).json({ error: 'Equipment ID is required' });
      return;
    }

    // Vérifier les permissions
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const updateData = { updatedAt: admin.firestore.Timestamp.now() };
    if (status) updateData.status = status;
    if (availableQuantity !== undefined) updateData.availableQuantity = availableQuantity;

    await db.collection('equipments').doc(equipmentId).update(updateData);

    res.status(200).json({ message: 'Equipment status updated successfully' });
  } catch (error) {
    logger.error("Error updating equipment status:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENT TYPES - CRUD Operations
// ============================================================================

// Récupérer les événements utilisant un type d'événement
exports.getEventsByEventType = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req);
    const { eventTypeId } = req.body.data;

    if (!eventTypeId) {
      res.status(400).json({ error: 'Event type ID is required' });
      return;
    }

    const eventsSnapshot = await db.collection('events')
      .where('eventTypeId', '==', eventTypeId)
      .get();

    const events = eventsSnapshot.docs.map(doc => ({
      id: doc.id,
      name: doc.data().name,
      startDateTime: doc.data().StartDateTime,
    }));

    res.status(200).json({ events });
  } catch (error) {
    logger.error("Error fetching events by type:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Créer un type d'événement (admin uniquement)
exports.createEventType = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdmin = await auth.hasPermission(decodedToken.uid, 'edit_data');

    if (!isAdmin) {
      res.status(403).json({ error: 'Forbidden: Admin permission required' });
      return;
    }

    const { name, defaultPrice } = req.body.data;

    if (!name || defaultPrice === undefined) {
      res.status(400).json({ error: 'Name and defaultPrice are required' });
      return;
    }

    // Vérifier l'unicité du nom
    const existingSnapshot = await db.collection('eventTypes')
      .where('name', '==', name)
      .get();

    if (!existingSnapshot.empty) {
      res.status(409).json({ error: 'Event type name already exists' });
      return;
    }

    const eventTypeData = {
      name,
      defaultPrice,
      createdAt: admin.firestore.Timestamp.now(),
    };

    const docRef = await db.collection('eventTypes').add(eventTypeData);

    res.status(201).json({ id: docRef.id, message: 'Event type created successfully' });
  } catch (error) {
    logger.error("Error creating event type:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Mettre à jour un type d'événement (admin uniquement)
exports.updateEventType = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdmin = await auth.hasPermission(decodedToken.uid, 'edit_data');

    if (!isAdmin) {
      res.status(403).json({ error: 'Forbidden: Admin permission required' });
      return;
    }

    const { eventTypeId, name, defaultPrice } = req.body.data;

    if (!eventTypeId) {
      res.status(400).json({ error: 'Event type ID is required' });
      return;
    }

    // Vérifier que le document existe
    const docRef = db.collection('eventTypes').doc(eventTypeId);
    const doc = await docRef.get();

    if (!doc.exists) {
      res.status(404).json({ error: 'Event type not found' });
      return;
    }

    // Vérifier l'unicité du nom (sauf pour le document actuel)
    if (name) {
      const existingSnapshot = await db.collection('eventTypes')
        .where('name', '==', name)
        .get();

      const hasDuplicate = existingSnapshot.docs.some(d => d.id !== eventTypeId);
      if (hasDuplicate) {
        res.status(409).json({ error: 'Event type name already exists' });
        return;
      }
    }

    const updateData = {};
    if (name) updateData.name = name;
    if (defaultPrice !== undefined) updateData.defaultPrice = defaultPrice;

    await docRef.update(updateData);

    res.status(200).json({ message: 'Event type updated successfully' });
  } catch (error) {
    logger.error("Error updating event type:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Supprimer un type d'événement (admin uniquement)
exports.deleteEventType = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const isAdmin = await auth.hasPermission(decodedToken.uid, 'edit_data');

    if (!isAdmin) {
      res.status(403).json({ error: 'Forbidden: Admin permission required' });
      return;
    }

    const { eventTypeId } = req.body.data;

    if (!eventTypeId) {
      res.status(400).json({ error: 'Event type ID is required' });
      return;
    }

    // Vérifier qu'aucun événement futur n'utilise ce type
    const eventsSnapshot = await db.collection('events')
      .where('eventTypeId', '==', eventTypeId)
      .get();

    const now = admin.firestore.Timestamp.now();
    const futureEvents = eventsSnapshot.docs.filter(doc => {
      const startDate = doc.data().StartDateTime;
      return startDate && startDate > now;
    });

    if (futureEvents.length > 0) {
      res.status(409).json({
        error: 'Cannot delete event type with future events',
        futureEventsCount: futureEvents.length
      });
      return;
    }

    await db.collection('eventTypes').doc(eventTypeId).delete();

    res.status(200).json({ message: 'Event type deleted successfully' });
  } catch (error) {
    logger.error("Error deleting event type:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENTS - Read with permissions
// ============================================================================
exports.getEvents = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { userId } = req.body.data || {};

    // Vérifier si l'utilisateur peut voir tous les événements
    const canViewAll = await auth.hasPermission(decodedToken.uid, 'view_all_events');

    let eventsSnapshot;

    if (canViewAll) {
      // Admin : tous les événements
      eventsSnapshot = await db.collection('events').get();
    } else {
      // Utilisateur normal : seulement ses événements assignés
      const userRef = db.collection('users').doc(userId || decodedToken.uid);
      eventsSnapshot = await db.collection('events')
        .where('workforce', 'array-contains', userRef)
        .get();
    }

    // Collecter tous les UIDs utilisateurs uniques
    const userIdsSet = new Set();

    eventsSnapshot.docs.forEach(doc => {
      const data = doc.data();
      if (data.workforce && Array.isArray(data.workforce)) {
        data.workforce.forEach(userRef => {
          if (userRef && userRef.id) {
            userIdsSet.add(userRef.id);
          } else if (typeof userRef === 'string' && userRef.startsWith('users/')) {
            userIdsSet.add(userRef.split('/')[1]);
          }
        });
      }
    });

    // Récupérer tous les utilisateurs en PARALLÈLE (optimisé)
    const usersMap = {};
    if (userIdsSet.size > 0) {
      const userIds = Array.from(userIdsSet);
      const batchSize = 30; // Augmenté de 10 à 30 pour réduire le nombre de requêtes

      // Exécuter les requêtes en PARALLÈLE au lieu de séquentiel
      const batchPromises = [];
      for (let i = 0; i < userIds.length; i += batchSize) {
        const batch = userIds.slice(i, i + batchSize);
        batchPromises.push(
          db.collection('users')
            .where(admin.firestore.FieldPath.documentId(), 'in', batch)
            .get()
        );
      }

      const results = await Promise.all(batchPromises);
      results.forEach(usersSnapshot => {
        usersSnapshot.docs.forEach(userDoc => {
          const userData = userDoc.data();
          // Stocker uniquement les données publiques
          usersMap[userDoc.id] = {
            uid: userDoc.id,
            firstName: userData.firstName || '',
            lastName: userData.lastName || '',
            email: userData.email || '',
            phoneNumber: userData.phoneNumber || '',
            profilePhotoUrl: userData.profilePhotoUrl || '',
          };
        });
      });
    }

    // Sérialiser les événements avec workforce comme liste d'UIDs
    const events = eventsSnapshot.docs.map(doc => {
      const data = doc.data();

      // Convertir workforce en liste d'UIDs
      let workforceUids = [];
      if (data.workforce && Array.isArray(data.workforce)) {
        workforceUids = data.workforce.map(userRef => {
          if (userRef && userRef.id) {
            return userRef.id;
          } else if (typeof userRef === 'string' && userRef.startsWith('users/')) {
            return userRef.split('/')[1];
          }
          return null;
        }).filter(uid => uid !== null);
      }

      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data),
        workforce: workforceUids, // Liste d'UIDs au lieu de DocumentReference
      };
    });

    // Retourner events + users map
    res.status(200).json({
      events,
      users: usersMap // Map UID -> données utilisateur
    });
  } catch (error) {
    logger.error("Error fetching events:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENTS - Get by month (optimized lazy loading)
// ============================================================================

/**
 * Récupère les événements d'un mois spécifique (lazy loading optimisé)
 * Réduit drastiquement le temps de chargement en ne chargeant que le mois demandé
 */
exports.getEventsByMonth = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { userId, year, month } = req.body.data || {};

    if (!year || !month) {
      res.status(400).json({ error: 'year and month are required' });
      return;
    }

    logger.info(`Fetching events for ${year}-${month}`);

    // Calculer le début et la fin du mois
    const startOfMonth = admin.firestore.Timestamp.fromDate(
      new Date(year, month - 1, 1, 0, 0, 0)
    );
    const endOfMonth = admin.firestore.Timestamp.fromDate(
      new Date(year, month, 0, 23, 59, 59)
    );

    // Vérifier si l'utilisateur peut voir tous les événements
    const canViewAll = await auth.hasPermission(decodedToken.uid, 'view_all_events');

    let eventsQuery = db.collection('events')
      .where('StartDateTime', '>=', startOfMonth)
      .where('StartDateTime', '<=', endOfMonth);

    if (!canViewAll) {
      // Utilisateur normal : seulement ses événements assignés
      const userRef = db.collection('users').doc(userId || decodedToken.uid);
      eventsQuery = eventsQuery.where('workforce', 'array-contains', userRef);
    }

    const eventsSnapshot = await eventsQuery.get();

    logger.info(`Found ${eventsSnapshot.docs.length} events for ${year}-${month}`);

    // Collecter tous les UIDs utilisateurs uniques
    const userIdsSet = new Set();

    eventsSnapshot.docs.forEach(doc => {
      const data = doc.data();
      if (data.workforce && Array.isArray(data.workforce)) {
        data.workforce.forEach(userRef => {
          if (userRef && userRef.id) {
            userIdsSet.add(userRef.id);
          } else if (typeof userRef === 'string' && userRef.startsWith('users/')) {
            userIdsSet.add(userRef.split('/')[1]);
          }
        });
      }
    });

    // Récupérer tous les utilisateurs en PARALLÈLE (optimisé)
    const usersMap = {};
    if (userIdsSet.size > 0) {
      const userIds = Array.from(userIdsSet);
      const batchSize = 30; // Limite Firestore augmentée de 10 à 30

      // Exécuter les requêtes en parallèle au lieu de séquentiel
      const batchPromises = [];
      for (let i = 0; i < userIds.length; i += batchSize) {
        const batch = userIds.slice(i, i + batchSize);
        batchPromises.push(
          db.collection('users')
            .where(admin.firestore.FieldPath.documentId(), 'in', batch)
            .get()
        );
      }

      const results = await Promise.all(batchPromises);
      results.forEach(usersSnapshot => {
        usersSnapshot.docs.forEach(userDoc => {
          const userData = userDoc.data();
          usersMap[userDoc.id] = {
            uid: userDoc.id,
            firstName: userData.firstName || '',
            lastName: userData.lastName || '',
            email: userData.email || '',
            phoneNumber: userData.phoneNumber || '',
            profilePhotoUrl: userData.profilePhotoUrl || '',
          };
        });
      });
    }

    // Sérialiser les événements avec workforce comme liste d'UIDs
    const events = eventsSnapshot.docs.map(doc => {
      const data = doc.data();

      // Convertir workforce en liste d'UIDs
      let workforceUids = [];
      if (data.workforce && Array.isArray(data.workforce)) {
        workforceUids = data.workforce.map(userRef => {
          if (userRef && userRef.id) {
            return userRef.id;
          } else if (typeof userRef === 'string' && userRef.startsWith('users/')) {
            return userRef.split('/')[1];
          }
          return null;
        }).filter(uid => uid !== null);
      }

      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data),
        workforce: workforceUids,
      };
    });

    logger.info(`Returning ${events.length} events with ${Object.keys(usersMap).length} unique users`);

    res.status(200).json({
      events,
      users: usersMap,
      month: { year, month }
    });
  } catch (error) {
    logger.error("Error fetching events by month:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Récupère un événement avec tous les détails (équipements complets + containers avec enfants)
 * Optimisé pour la page de préparation et l'affichage détaillé
 */
exports.getEventWithDetails = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { eventId } = req.body.data || {};

    if (!eventId) {
      res.status(400).json({ error: 'eventId is required' });
      return;
    }

    // Récupérer l'événement
    const eventDoc = await db.collection('events').doc(eventId).get();

    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();

    // Vérifier les permissions
    const canViewAll = await auth.hasPermission(decodedToken.uid, 'view_all_events');
    if (!canViewAll) {
      // Vérifier si l'utilisateur est dans la workforce
      const userRef = db.collection('users').doc(decodedToken.uid);
      const isInWorkforce = eventData.workforce && eventData.workforce.some(ref =>
        (ref.id && ref.id === decodedToken.uid) ||
        (typeof ref === 'string' && ref === `users/${decodedToken.uid}`)
      );

      if (!isInWorkforce) {
        res.status(403).json({ error: 'Forbidden: Not assigned to this event' });
        return;
      }
    }

    logger.info(`[getEventWithDetails] Loading details for event ${eventId}`);

    // Collecter tous les IDs d'équipements et de containers
    const equipmentIds = new Set();
    const containerIds = new Set();

    if (eventData.assignedEquipment && Array.isArray(eventData.assignedEquipment)) {
      eventData.assignedEquipment.forEach(eq => {
        if (eq.equipmentId) {
          equipmentIds.add(eq.equipmentId);
        }
      });
    }

    if (eventData.assignedContainers && Array.isArray(eventData.assignedContainers)) {
      eventData.assignedContainers.forEach(id => containerIds.add(id));
    }

    logger.info(`[getEventWithDetails] Loading ${equipmentIds.size} equipments and ${containerIds.size} containers`);

    // Charger tous les équipements en parallèle
    const equipmentPromises = Array.from(equipmentIds).map(id =>
      db.collection('equipments').doc(id).get()
    );
    const equipmentDocs = await Promise.all(equipmentPromises);

    const equipmentMap = {};
    for (const doc of equipmentDocs) {
      if (doc.exists) {
        let data = { id: doc.id, ...doc.data() };
        data = helpers.serializeTimestamps(data);
        data = helpers.serializeReferences(data);
        equipmentMap[doc.id] = data;
      }
    }

    // Charger tous les containers en parallèle
    const containerPromises = Array.from(containerIds).map(id =>
      db.collection('containers').doc(id).get()
    );
    const containerDocs = await Promise.all(containerPromises);

    // Collecter les IDs des équipements enfants des containers
    const childEquipmentIds = new Set();
    for (const doc of containerDocs) {
      if (doc.exists) {
        const containerData = doc.data();
        if (containerData.equipmentIds && Array.isArray(containerData.equipmentIds)) {
          containerData.equipmentIds.forEach(id => childEquipmentIds.add(id));
        }
      }
    }

    logger.info(`[getEventWithDetails] Loading ${childEquipmentIds.size} child equipments from containers`);

    // Charger les équipements enfants des containers
    const childEquipmentPromises = Array.from(childEquipmentIds).map(id =>
      db.collection('equipments').doc(id).get()
    );
    const childEquipmentDocs = await Promise.all(childEquipmentPromises);

    // Ajouter les enfants au map d'équipements
    for (const doc of childEquipmentDocs) {
      if (doc.exists && !equipmentMap[doc.id]) {
        let data = { id: doc.id, ...doc.data() };
        data = helpers.serializeTimestamps(data);
        data = helpers.serializeReferences(data);
        equipmentMap[doc.id] = data;
      }
    }

    // Construire les containers avec leurs enfants complets
    const containerMap = {};
    for (const doc of containerDocs) {
      if (doc.exists) {
        let containerData = { id: doc.id, ...doc.data() };
        containerData = helpers.serializeTimestamps(containerData);
        containerData = helpers.serializeReferences(containerData);

        // Ajouter les équipements enfants complets
        if (containerData.equipmentIds && Array.isArray(containerData.equipmentIds)) {
          containerData.children = containerData.equipmentIds
            .map(id => equipmentMap[id])
            .filter(eq => eq !== undefined);
        } else {
          containerData.children = [];
        }

        containerMap[doc.id] = containerData;
      }
    }

    // Construire la réponse finale
    const event = {
      id: eventDoc.id,
      ...helpers.serializeTimestamps(eventData),
      workforce: eventData.workforce ? eventData.workforce.map(ref =>
        (ref.id || (typeof ref === 'string' ? ref.split('/')[1] : null))
      ).filter(uid => uid !== null) : [],
    };

    logger.info(`[getEventWithDetails] Returning event with ${Object.keys(equipmentMap).length} equipments and ${Object.keys(containerMap).length} containers`);

    res.status(200).json({
      event,
      equipments: equipmentMap,
      containers: containerMap,
    });
  } catch (error) {
    logger.error("Error getting event with details:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// MAINTENANCES - Read with permissions
// ============================================================================
exports.getMaintenances = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const { equipmentId } = req.body.data || {};

    // Vérifier les permissions
    const canView = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!canView) {
      res.status(403).json({ error: 'Forbidden: Requires equipment permissions' });
      return;
    }

    let query = db.collection('maintenances');

    // Filtrer par équipement si spécifié
    if (equipmentId) {
      query = query.where('equipmentIds', 'array-contains', equipmentId);
    }

    const snapshot = await query.get();
    const maintenances = snapshot.docs.map(doc => {
      const data = doc.data();
      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data, ['scheduledDate', 'completedDate', 'createdAt', 'updatedAt'])
      };
    });

    res.status(200).json({ maintenances });
  } catch (error) {
    logger.error("Error fetching maintenances:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// ALERTS - Read with permissions
// ============================================================================
exports.getAlerts = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req);

    const snapshot = await db.collection('alerts')
      .orderBy('createdAt', 'desc')
      .limit(100)
      .get();

    const alerts = snapshot.docs.map(doc => {
      const data = doc.data();
      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data, ['createdAt'])
      };
    });

    res.status(200).json({ alerts });
  } catch (error) {
    logger.error("Error fetching alerts:", error);
    res.status(500).json({ error: error.message });
  }
}));


exports.markAlertAsRead = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req);

    const alertId = req.body.data?.alertId;
    if (!alertId) {
      res.status(400).json({ error: 'alertId is required' });
      return;
    }

    await db.collection('alerts').doc(alertId).update({
      isRead: true
    });

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error marking alert as read:", error);
    res.status(500).json({ error: error.message });
  }
}));


exports.deleteAlert = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    await auth.authenticateUser(req);

    const alertId = req.body.data?.alertId;
    if (!alertId) {
      res.status(400).json({ error: 'alertId is required' });
      return;
    }

    await db.collection('alerts').doc(alertId).delete();

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error deleting alert:", error);
    res.status(500).json({ error: error.message });
  }
}));

// createAlert est défini dans createAlert.js et importé à la fin du fichier

// ============================================================================
// USERS - Read with permissions
// ============================================================================
exports.getUsers = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    // Vérifier les permissions
    const canViewAll = await auth.hasPermission(decodedToken.uid, 'view_all_users');

    if (!canViewAll) {
      // Si pas admin, ne retourner que l'utilisateur lui-même
      const userDoc = await db.collection('users').doc(decodedToken.uid).get();

      if (!userDoc.exists) {
        res.status(404).json({ error: 'User not found' });
        return;
      }

      let userData = userDoc.data();
      userData = helpers.serializeTimestamps(userData);
      userData = helpers.serializeReferences(userData);

      res.status(200).json({
        users: [{
          id: userDoc.id,
          ...userData
        }]
      });
      return;
    }

    // Admin : tous les utilisateurs
    const snapshot = await db.collection('users').get();
    const users = snapshot.docs.map(doc => {
      let data = doc.data();
      data = helpers.serializeTimestamps(data);
      data = helpers.serializeReferences(data);
      return {
        id: doc.id,
        ...data
      };
    });

    res.status(200).json({ users });
  } catch (error) {
    logger.error("Error fetching users:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// USER - Récupération individuelle
// ============================================================================


/**
 * Récupère un utilisateur spécifique par son ID
 * Tout utilisateur authentifié peut accéder aux données publiques
 */
exports.getUser = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    const { userId } = req.body.data || req.body || {};
    if (!userId) {
      res.status(400).json({ error: 'userId is required' });
      return;
    }

    const userDoc = await db.collection('users').doc(userId).get();
    if (!userDoc.exists) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    const user = userDoc.data();

    // Données publiques accessibles à tous
    const userData = {
      id: userDoc.id,
      uid: user.uid || userDoc.id,
      email: user.email || '',
      firstName: user.firstName || '',
      lastName: user.lastName || '',
      phoneNumber: user.phoneNumber || '',
      profilePhotoUrl: user.profilePhotoUrl || '',
    };

    // Inclure le rôle si disponible
    if (user.role) {
      const roleDoc = await user.role.get();
      if (roleDoc.exists) {
        userData.role = {
          id: roleDoc.id,
          ...roleDoc.data(),
        };
      }
    }

    res.status(200).json({ user: userData });
  } catch (error) {
    logger.error('Error fetching user:', error);
    res.status(500).json({ error: error.message });
  }
}));


// ============================================================================
// EQUIPMENT AVAILABILITY - Vérification de disponibilité
// ============================================================================

exports.checkEquipmentAvailability = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentId, startDate, endDate, excludeEventId } = req.body.data;

    if (!equipmentId || !startDate || !endDate) {
      res.status(400).json({ error: 'equipmentId, startDate, and endDate are required' });
      return;
    }

    logger.info(`Checking availability for equipment ${equipmentId} from ${startDate} to ${endDate}, excluding event: ${excludeEventId}`);

    const startTimestamp = admin.firestore.Timestamp.fromDate(new Date(startDate));
    const endTimestamp = admin.firestore.Timestamp.fromDate(new Date(endDate));

    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    logger.info(`Found ${eventsSnapshot.docs.length} events to check`);

    const conflicts = [];

    for (const eventDoc of eventsSnapshot.docs) {
      const event = eventDoc.data();

      if (excludeEventId && eventDoc.id === excludeEventId) {
        continue;
      }

      // Gérer les dates qui peuvent être des Timestamps ou des objets Date
      let eventStart, eventEnd;
      if (event.StartDateTime) {
        eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
      }
      if (event.EndDateTime) {
        eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
      }


      if (!eventStart || !eventEnd) {
        continue;
      }

      // Vérifier si l'équipement est assigné à cet événement (directement ou via une boîte)
      const assignedEquipment = event.assignedEquipment || [];
      const assignedContainers = event.assignedContainers || [];

      // Vérifier si l'équipement est directement assigné
      const isEquipmentDirectlyAssigned = assignedEquipment.some(eq => eq.equipmentId === equipmentId);

      // Vérifier si l'équipement est dans une boîte assignée
      let isEquipmentInAssignedContainer = false;
      if (assignedContainers.length > 0) {
        logger.info(`Event ${eventDoc.id} has ${assignedContainers.length} assigned containers`);
        // Récupérer les conteneurs assignés et vérifier si l'équipement y est
        for (const containerId of assignedContainers) {
          const containerDoc = await db.collection('containers').doc(containerId).get();
          if (containerDoc.exists) {
            const containerData = containerDoc.data();
            const equipmentIds = containerData.equipmentIds || [];
            logger.info(`Container ${containerId} contains equipment IDs: ${equipmentIds.join(', ')}`);
            if (equipmentIds.includes(equipmentId)) {
              isEquipmentInAssignedContainer = true;
              logger.info(`Equipment ${equipmentId} found in container ${containerId} for event ${eventDoc.id}`);
              break;
            }
          }
        }
      }

      if (isEquipmentDirectlyAssigned) {
        logger.info(`Equipment ${equipmentId} is directly assigned to event ${eventDoc.id}`);
      }

      if (!isEquipmentDirectlyAssigned && !isEquipmentInAssignedContainer) {
        continue;
      }

      // Vérifier le chevauchement de dates
      const requestStart = startTimestamp.toDate();
      const requestEnd = endTimestamp.toDate();

      // Inclure les temps d'installation et de démontage
      const installationTime = event.InstallationTime || 0;
      const disassemblyTime = event.DisassemblyTime || 0;

      const eventStartWithSetup = new Date(eventStart);
      eventStartWithSetup.setHours(eventStartWithSetup.getHours() - installationTime);

      const eventEndWithTeardown = new Date(eventEnd);
      eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + disassemblyTime);

      // Il y a conflit si les périodes se chevauchent
      const hasOverlap = requestStart < eventEndWithTeardown && requestEnd > eventStartWithSetup;

      if (hasOverlap) {
        // Calculer les jours de chevauchement
        const overlapStart = new Date(Math.max(requestStart, eventStartWithSetup));
        const overlapEnd = new Date(Math.min(requestEnd, eventEndWithTeardown));
        const overlapDays = Math.ceil((overlapEnd - overlapStart) / (1000 * 60 * 60 * 24));

        logger.info(`Conflict detected: Equipment ${equipmentId} conflicts with event ${eventDoc.id} (${event.Name})`);

        // Retourner les détails complets de l'événement
        const eventData = helpers.serializeTimestamps(event);
        conflicts.push({
          eventId: eventDoc.id,
          eventName: event.Name,
          eventData: eventData, // Ajouter toutes les données de l'événement
          startDate: eventStart.toISOString(),
          endDate: eventEnd.toISOString(),
          overlapDays: overlapDays
        });
      }
    }

    logger.info(`Total conflicts found: ${conflicts.length}`);

    res.status(200).json({ conflicts, available: conflicts.length === 0 });
  } catch (error) {
    logger.error("Error checking equipment availability:", error);
    res.status(500).json({ error: error.message || "Failed to check equipment availability" });
  }
}));

exports.checkContainerAvailability = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { containerId, startDate, endDate, excludeEventId } = req.body.data;

    if (!containerId || !startDate || !endDate) {
      res.status(400).json({ error: 'containerId, startDate, and endDate are required' });
      return;
    }

    // Récupérer le container et ses équipements
    const containerDoc = await db.collection('containers').doc(containerId).get();
    if (!containerDoc.exists) {
      throw new Error('Container not found');
    }

    const containerData = containerDoc.data();
    const equipmentIds = containerData.equipmentIds || [];

    const startTimestamp = admin.firestore.Timestamp.fromDate(new Date(startDate));
    const endTimestamp = admin.firestore.Timestamp.fromDate(new Date(endDate));

    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    const containerConflicts = [];
    const equipmentConflicts = {};

    for (const eventDoc of eventsSnapshot.docs) {
      const event = eventDoc.data();

      if (excludeEventId && eventDoc.id === excludeEventId) {
        continue;
      }

      // Gérer les dates
      let eventStart, eventEnd;
      if (event.StartDateTime) {
        eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
      }
      if (event.EndDateTime) {
        eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
      }

      if (!eventStart || !eventEnd) {
        continue;
      }

      // Vérifier si le container est assigné
      const assignedContainers = event.assignedContainers || [];
      const isContainerAssigned = assignedContainers.includes(containerId);

      // Vérifier si des équipements du container sont assignés
      const assignedEquipment = event.assignedEquipment || [];
      const conflictingEquipmentIds = equipmentIds.filter(eqId =>
        assignedEquipment.some(eq => eq.equipmentId === eqId)
      );

      if (!isContainerAssigned && conflictingEquipmentIds.length === 0) {
        continue;
      }

      // Vérifier le chevauchement de dates
      const requestStart = startTimestamp.toDate();
      const requestEnd = endTimestamp.toDate();

      const installationTime = event.InstallationTime || 0;
      const disassemblyTime = event.DisassemblyTime || 0;

      const eventStartWithSetup = new Date(eventStart);
      eventStartWithSetup.setHours(eventStartWithSetup.getHours() - installationTime);

      const eventEndWithTeardown = new Date(eventEnd);
      eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + disassemblyTime);

      const hasOverlap = requestStart < eventEndWithTeardown && requestEnd > eventStartWithSetup;

      if (hasOverlap) {
        const overlapStart = new Date(Math.max(requestStart, eventStartWithSetup));
        const overlapEnd = new Date(Math.min(requestEnd, eventEndWithTeardown));
        const overlapDays = Math.ceil((overlapEnd - overlapStart) / (1000 * 60 * 60 * 24));

        const conflictInfo = {
          eventId: eventDoc.id,
          eventName: event.Name,
          startDate: eventStart.toISOString(),
          endDate: eventEnd.toISOString(),
          overlapDays: overlapDays
        };

        if (isContainerAssigned) {
          containerConflicts.push(conflictInfo);
        }

        conflictingEquipmentIds.forEach(eqId => {
          if (!equipmentConflicts[eqId]) {
            equipmentConflicts[eqId] = [];
          }
          equipmentConflicts[eqId].push(conflictInfo);
        });
      }
    }

    const hasContainerConflict = containerConflicts.length > 0;
    const hasPartialConflict = Object.keys(equipmentConflicts).length > 0 && !hasContainerConflict;
    const conflictType = hasContainerConflict ? 'complete' : (hasPartialConflict ? 'partial' : 'none');

    res.status(200).json({
      conflictType,
      containerConflicts,
      equipmentConflicts,
      isAvailable: conflictType === 'none'
    });
  } catch (error) {
    logger.error("Error checking container availability:", error);
    res.status(500).json({ error: error.message || "Failed to check container availability" });
  }
}));

// ============================================================================
// AVAILABILITY - Optimized batch check
// ============================================================================

/**
 * Récupère tous les équipements et conteneurs en conflit pour une période donnée
 * Optimisé : une seule requête au lieu d'une par équipement
 */
exports.getConflictingEquipmentIds = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { startDate, endDate, excludeEventId, installationTime = 0, disassemblyTime = 0 } = req.body.data;

    if (!startDate || !endDate) {
      res.status(400).json({ error: 'startDate and endDate are required' });
      return;
    }

    logger.info(`Getting conflicting equipment IDs for period ${startDate} to ${endDate}`);

    // Calculer la période effective avec temps de montage/démontage
    const requestStartDate = new Date(startDate);
    requestStartDate.setHours(requestStartDate.getHours() - installationTime);

    const requestEndDate = new Date(endDate);
    requestEndDate.setHours(requestEndDate.getHours() + disassemblyTime);

    // Récupérer tous les événements non annulés
    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    logger.info(`Found ${eventsSnapshot.docs.length} events to check`);

    // Récupérer tous les équipements pour savoir lesquels sont quantifiables
    const equipmentsSnapshot = await db.collection('equipments').get();
    const equipmentsInfo = {};
    equipmentsSnapshot.docs.forEach(doc => {
      const data = doc.data();
      equipmentsInfo[doc.id] = {
        category: data.category,
        totalQuantity: data.totalQuantity || 0,
        hasQuantity: data.category === 'CABLE' || data.category === 'CONSUMABLE'
      };
    });

    // Maps pour stocker les conflits
    const conflictingEquipmentIds = new Set();
    const conflictingContainerIds = new Set();
    const conflictDetails = {}; // { equipmentId/containerId: [{ eventId, eventName, startDate, endDate, quantity }] }
    const equipmentQuantities = {}; // { equipmentId: { totalQuantity, reservedQuantity, availableQuantity, reservations: [...] } }

    for (const eventDoc of eventsSnapshot.docs) {
      // Exclure l'événement en cours d'édition
      if (excludeEventId && eventDoc.id === excludeEventId) {
        continue;
      }

      const event = eventDoc.data();

      // Gérer les dates
      let eventStart, eventEnd;
      if (event.StartDateTime) {
        eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
      }
      if (event.EndDateTime) {
        eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
      }

      if (!eventStart || !eventEnd) {
        continue;
      }

      // Ajouter temps de montage/démontage de cet événement
      const eventInstallTime = event.InstallationTime || 0;
      const eventDisassemblyTime = event.DisassemblyTime || 0;

      const eventStartWithSetup = new Date(eventStart);
      eventStartWithSetup.setHours(eventStartWithSetup.getHours() - eventInstallTime);

      const eventEndWithTeardown = new Date(eventEnd);
      eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + eventDisassemblyTime);

      // Vérifier le chevauchement de dates
      const hasOverlap = requestStartDate < eventEndWithTeardown && requestEndDate > eventStartWithSetup;

      if (!hasOverlap) {
        continue;
      }

      // Il y a chevauchement ! Récupérer les équipements et conteneurs assignés
      const assignedEquipment = event.assignedEquipment || [];
      const assignedContainers = event.assignedContainers || [];

      const conflictInfo = {
        eventId: eventDoc.id,
        eventName: event.Name,
        startDate: eventStart.toISOString(),
        endDate: eventEnd.toISOString(),
      };

      // Ajouter les équipements directement assignés
      for (const eq of assignedEquipment) {
        const equipmentId = eq.equipmentId;
        const quantity = eq.quantity || 1;
        const equipInfo = equipmentsInfo[equipmentId];

        // Pour les équipements quantifiables, on ne les marque pas forcément comme "en conflit"
        // On calcule juste les quantités réservées
        if (equipInfo && equipInfo.hasQuantity) {
          // Initialiser les infos de quantité si nécessaire
          if (!equipmentQuantities[equipmentId]) {
            equipmentQuantities[equipmentId] = {
              totalQuantity: equipInfo.totalQuantity,
              reservedQuantity: 0,
              availableQuantity: equipInfo.totalQuantity,
              reservations: []
            };
          }

          // Ajouter la réservation
          equipmentQuantities[equipmentId].reservedQuantity += quantity;
          equipmentQuantities[equipmentId].availableQuantity = equipInfo.totalQuantity - equipmentQuantities[equipmentId].reservedQuantity;
          equipmentQuantities[equipmentId].reservations.push({
            ...conflictInfo,
            quantity: quantity
          });

          // Ne marquer comme "en conflit" que si la quantité totale est épuisée
          if (equipmentQuantities[equipmentId].availableQuantity <= 0) {
            conflictingEquipmentIds.add(equipmentId);
          }
        } else {
          // Pour les équipements non quantifiables, comportement classique
          conflictingEquipmentIds.add(equipmentId);
        }

        if (!conflictDetails[equipmentId]) {
          conflictDetails[equipmentId] = [];
        }
        conflictDetails[equipmentId].push({
          ...conflictInfo,
          quantity: quantity
        });
      }

      // Ajouter les conteneurs assignés
      for (const containerId of assignedContainers) {
        conflictingContainerIds.add(containerId);

        if (!conflictDetails[containerId]) {
          conflictDetails[containerId] = [];
        }
        conflictDetails[containerId].push(conflictInfo);

        // Récupérer les équipements dans ce conteneur
        const containerDoc = await db.collection('containers').doc(containerId).get();
        if (containerDoc.exists) {
          const containerData = containerDoc.data();
          const equipmentIds = containerData.equipmentIds || [];

          // Marquer tous les équipements du conteneur comme en conflit
          for (const equipmentId of equipmentIds) {
            conflictingEquipmentIds.add(equipmentId);

            if (!conflictDetails[equipmentId]) {
              conflictDetails[equipmentId] = [];
            }
            // Ajouter une note indiquant que c'est via le conteneur
            conflictDetails[equipmentId].push({
              ...conflictInfo,
              viaContainer: containerId,
              viaContainerName: containerData.name || 'Conteneur inconnu',
            });
          }
        }
      }
    }

    logger.info(`Found ${conflictingEquipmentIds.size} conflicting equipment(s) and ${conflictingContainerIds.size} conflicting container(s)`);

    res.status(200).json({
      conflictingEquipmentIds: Array.from(conflictingEquipmentIds),
      conflictingContainerIds: Array.from(conflictingContainerIds),
      conflictDetails: conflictDetails,
      equipmentQuantities: equipmentQuantities, // NOUVEAU : Informations sur les quantités
    });
  } catch (error) {
    logger.error("Error getting conflicting equipment IDs:", error);
    res.status(500).json({ error: error.message || "Failed to get conflicting equipment IDs" });
  }
}));

// ============================================================================
// USER - Get current authenticated user
// ============================================================================
exports.getCurrentUser = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const userId = decodedToken.uid;

    const userDoc = await db.collection('users').doc(userId).get();
    if (!userDoc.exists) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    const userData = userDoc.data();

    // Récupérer le rôle
    let roleData = null;
    if (userData.role) {
      const roleDoc = await userData.role.get();
      if (roleDoc.exists) {
        roleData = { id: roleDoc.id, ...roleDoc.data() };
      }
    }

    res.status(200).json({
      user: {
        uid: userId,
        ...helpers.serializeTimestamps(userData),
        role: roleData
      }
    });
  } catch (error) {
    logger.error("Error getting current user:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// MAINTENANCE - Delete
// ============================================================================
exports.deleteMaintenance = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    // Vérifier permission
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const maintenanceId = req.body.data?.maintenanceId;
    if (!maintenanceId) {
      res.status(400).json({ error: 'maintenanceId is required' });
      return;
    }

    // Récupérer la maintenance pour connaître les équipements
    const maintenanceDoc = await db.collection('maintenances').doc(maintenanceId).get();
    if (maintenanceDoc.exists) {
      const maintenance = maintenanceDoc.data();

      // Retirer la maintenance des équipements
      if (maintenance.equipmentIds) {
        for (const equipmentId of maintenance.equipmentIds) {
          const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
          if (equipmentDoc.exists) {
            const equipmentData = equipmentDoc.data();
            const maintenanceIds = (equipmentData.maintenanceIds || []).filter(id => id !== maintenanceId);
            await db.collection('equipments').doc(equipmentId).update({ maintenanceIds });
          }
        }
      }
    }

    await db.collection('maintenances').doc(maintenanceId).delete();

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error deleting maintenance:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// EVENT PREPARATION - Validation des étapes de préparation
// ============================================================================

// Helper: Mettre à jour le statut d'un équipement
async function updateEquipmentStatus(equipmentId, status) {
  try {
    const doc = await db.collection('equipments').doc(equipmentId).get();
    if (!doc.exists) {
      logger.warn(`Equipment ${equipmentId} does not exist, skipping status update`);
      return;
    }

    await db.collection('equipments').doc(equipmentId).update({
      status: status,
      updatedAt: admin.firestore.Timestamp.now(),
    });
  } catch (error) {
    logger.error(`Error updating equipment status for ${equipmentId}:`, error);
  }
}

// Valider un équipement individuel en préparation
exports.validateEquipmentPreparation = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId, equipmentId } = req.body.data;
    if (!eventId || !equipmentId) {
      res.status(400).json({ error: 'eventId and equipmentId are required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    // Mettre à jour le statut de l'équipement
    const updatedEquipment = assignedEquipment.map(eq => {
      if (eq.equipmentId === equipmentId) {
        return { ...eq, isPrepared: true };
      }
      return eq;
    });

    // Vérifier si tous sont préparés
    const allPrepared = updatedEquipment.every(eq => eq.isPrepared);

    const updateData = {
      assignedEquipment: updatedEquipment,
      preparationStatus: allPrepared ? 'completed' : 'inProgress',
    };

    await db.collection('events').doc(eventId).update(updateData);

    res.status(200).json({ success: true, allPrepared });
  } catch (error) {
    logger.error("Error validating equipment preparation:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider tous les équipements en préparation
exports.validateAllPreparation = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId } = req.body.data;
    if (!eventId) {
      res.status(400).json({ error: 'eventId is required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    // Marquer tous comme préparés
    const updatedEquipment = assignedEquipment.map(eq => ({
      ...eq,
      isPrepared: true,
    }));

    await db.collection('events').doc(eventId).update({
      assignedEquipment: updatedEquipment,
      preparationStatus: 'completed',
    });

    // Mettre à jour le statut des équipements à "inUse"
    for (const equipment of assignedEquipment) {
      await updateEquipmentStatus(equipment.equipmentId, 'inUse');
    }

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error validating all preparation:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider un équipement individuel pour le chargement
exports.validateEquipmentLoading = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId, equipmentId } = req.body.data;
    if (!eventId || !equipmentId) {
      res.status(400).json({ error: 'eventId and equipmentId are required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => {
      if (eq.equipmentId === equipmentId) {
        return { ...eq, isLoaded: true };
      }
      return eq;
    });

    const allLoaded = updatedEquipment.every(eq => eq.isLoaded);

    const updateData = {
      assignedEquipment: updatedEquipment,
      loadingStatus: allLoaded ? 'completed' : 'inProgress',
    };

    await db.collection('events').doc(eventId).update(updateData);

    res.status(200).json({ success: true, allLoaded });
  } catch (error) {
    logger.error("Error validating equipment loading:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider tous les équipements pour le chargement
exports.validateAllLoading = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId } = req.body.data;
    if (!eventId) {
      res.status(400).json({ error: 'eventId is required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => ({
      ...eq,
      isLoaded: true,
    }));

    await db.collection('events').doc(eventId).update({
      assignedEquipment: updatedEquipment,
      loadingStatus: 'completed',
    });

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error validating all loading:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider un équipement individuel pour le déchargement
exports.validateEquipmentUnloading = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId, equipmentId } = req.body.data;
    if (!eventId || !equipmentId) {
      res.status(400).json({ error: 'eventId and equipmentId are required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => {
      if (eq.equipmentId === equipmentId) {
        return { ...eq, isUnloaded: true };
      }
      return eq;
    });

    const allUnloaded = updatedEquipment.every(eq => eq.isUnloaded);

    const updateData = {
      assignedEquipment: updatedEquipment,
      unloadingStatus: allUnloaded ? 'completed' : 'inProgress',
    };

    await db.collection('events').doc(eventId).update(updateData);

    res.status(200).json({ success: true, allUnloaded });
  } catch (error) {
    logger.error("Error validating equipment unloading:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider tous les équipements pour le déchargement
exports.validateAllUnloading = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId } = req.body.data;
    if (!eventId) {
      res.status(400).json({ error: 'eventId is required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => ({
      ...eq,
      isUnloaded: true,
    }));

    await db.collection('events').doc(eventId).update({
      assignedEquipment: updatedEquipment,
      unloadingStatus: 'completed',
    });

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error validating all unloading:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider un équipement individuel pour le retour
exports.validateEquipmentReturn = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId, equipmentId, returnedQuantity } = req.body.data;
    if (!eventId || !equipmentId) {
      res.status(400).json({ error: 'eventId and equipmentId are required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => {
      if (eq.equipmentId === equipmentId) {
        return {
          ...eq,
          isReturned: true,
          returnedQuantity: returnedQuantity !== undefined ? returnedQuantity : eq.returnedQuantity,
        };
      }
      return eq;
    });

    const allReturned = updatedEquipment.every(eq => eq.isReturned);

    const updateData = {
      assignedEquipment: updatedEquipment,
      returnStatus: allReturned ? 'completed' : 'inProgress',
    };

    await db.collection('events').doc(eventId).update(updateData);

    // Mettre à jour le stock si c'est un consommable
    if (returnedQuantity !== undefined) {
      const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
      if (equipmentDoc.exists) {
        const equipmentData = equipmentDoc.data();
        if (equipmentData.hasQuantity) {
          const currentAvailable = equipmentData.availableQuantity || 0;
          await db.collection('equipments').doc(equipmentId).update({
            availableQuantity: currentAvailable + returnedQuantity,
          });
        }
      }
    }

    res.status(200).json({ success: true, allReturned });
  } catch (error) {
    logger.error("Error validating equipment return:", error);
    res.status(500).json({ error: error.message });
  }
}));

// Valider tous les retours
exports.validateAllReturn = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_events');
    if (!canManage) {
      res.status(403).json({ error: 'Forbidden: Requires manage_events permission' });
      return;
    }

    const { eventId, returnedQuantities } = req.body.data;
    if (!eventId) {
      res.status(400).json({ error: 'eventId is required' });
      return;
    }

    const eventDoc = await db.collection('events').doc(eventId).get();
    if (!eventDoc.exists) {
      res.status(404).json({ error: 'Event not found' });
      return;
    }

    const eventData = eventDoc.data();
    const assignedEquipment = eventData.assignedEquipment || [];

    const updatedEquipment = assignedEquipment.map(eq => {
      const returnedQty = returnedQuantities?.[eq.equipmentId] || eq.returnedQuantity || eq.quantity;
      return {
        ...eq,
        isReturned: true,
        returnedQuantity: returnedQty,
      };
    });

    await db.collection('events').doc(eventId).update({
      assignedEquipment: updatedEquipment,
      returnStatus: 'completed',
    });

    // Mettre à jour le statut des équipements à "available" et gérer les stocks
    for (const equipment of updatedEquipment) {
      const equipmentDoc = await db.collection('equipments').doc(equipment.equipmentId).get();
      if (equipmentDoc.exists) {
        const equipmentData = equipmentDoc.data();

        // Mettre à jour le statut uniquement pour les équipements non quantifiables
        if (!equipmentData.hasQuantity) {
          await updateEquipmentStatus(equipment.equipmentId, 'available');
        }

        // Restaurer le stock pour les consommables
        if (equipmentData.hasQuantity && equipment.returnedQuantity) {
          const currentAvailable = equipmentData.availableQuantity || 0;
          await db.collection('equipments').doc(equipment.equipmentId).update({
            availableQuantity: currentAvailable + equipment.returnedQuantity,
            updatedAt: admin.firestore.Timestamp.now(),
          });
        }
      }
    }

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error validating all return:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// AVAILABILITY & STOCK CHECK
// ============================================================================

/**
 * Vérifier la disponibilité d'un équipement pour une période donnée
 */
exports.checkEquipmentAvailability = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentId, startDate, endDate } = req.body.data;

    if (!equipmentId || !startDate || !endDate) {
      res.status(400).json({ error: 'equipmentId, startDate and endDate are required' });
      return;
    }

    const start = admin.firestore.Timestamp.fromDate(new Date(startDate));
    const end = admin.firestore.Timestamp.fromDate(new Date(endDate));

    // Récupérer les événements qui chevauchent la période
    const eventsSnapshot = await db.collection('events')
      .where('StartDateTime', '<=', end)
      .where('EndDateTime', '>=', start)
      .where('status', '!=', 'CANCELLED')
      .get();

    const conflicts = [];

    eventsSnapshot.docs.forEach(doc => {
      const eventData = doc.data();
      const assignedEquipment = eventData.assignedEquipment || [];

      for (const eq of assignedEquipment) {
        if (eq.equipmentId === equipmentId) {
          conflicts.push({
            eventId: doc.id,
            eventName: eventData.Name || 'Sans nom',
            startDate: eventData.StartDateTime.toDate().toISOString(),
            endDate: eventData.EndDateTime.toDate().toISOString(),
          });
          break;
        }
      }
    });

    res.status(200).json({ conflicts });
  } catch (error) {
    logger.error("Error checking equipment availability:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Trouver des alternatives (même modèle) disponibles pour une période donnée
 */
exports.findAlternativeEquipment = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { model, startDate, endDate } = req.body.data;

    if (!model || !startDate || !endDate) {
      res.status(400).json({ error: 'model, startDate and endDate are required' });
      return;
    }

    const start = admin.firestore.Timestamp.fromDate(new Date(startDate));
    const end = admin.firestore.Timestamp.fromDate(new Date(endDate));

    // Récupérer tous les équipements du même modèle
    const equipmentsSnapshot = await db.collection('equipments')
      .where('model', '==', model)
      .get();

    // Récupérer tous les événements qui chevauchent la période
    const eventsSnapshot = await db.collection('events')
      .where('StartDateTime', '<=', end)
      .where('EndDateTime', '>=', start)
      .where('status', '!=', 'CANCELLED')
      .get();

    // Créer un set des équipements en conflit
    const conflictingEquipmentIds = new Set();
    eventsSnapshot.docs.forEach(doc => {
      const eventData = doc.data();
      const assignedEquipment = eventData.assignedEquipment || [];
      assignedEquipment.forEach(eq => conflictingEquipmentIds.add(eq.equipmentId));
    });

    // Filtrer les équipements disponibles
    const alternatives = [];
    equipmentsSnapshot.docs.forEach(doc => {
      const data = doc.data();
      if (!conflictingEquipmentIds.has(doc.id) && data.status === 'available') {
        alternatives.push({
          id: doc.id,
          ...helpers.serializeTimestamps(data, ['purchaseDate', 'nextMaintenanceDate', 'lastMaintenanceDate', 'createdAt', 'updatedAt'])
        });
      }
    });

    res.status(200).json({ alternatives });
  } catch (error) {
    logger.error("Error finding alternative equipment:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Calculer le statut réel d'un ou plusieurs équipements basé sur les événements en cours
 */
exports.calculateEquipmentStatuses = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const { equipmentIds } = req.body.data;

    if (!equipmentIds || !Array.isArray(equipmentIds)) {
      res.status(400).json({ error: 'equipmentIds array is required' });
      return;
    }

    // Récupérer tous les événements en cours (préparation complétée mais pas encore retournés)
    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    const equipmentIdsInUse = new Set();
    const containerIdsInUse = new Set();

    eventsSnapshot.docs.forEach(doc => {
      const event = doc.data();

      const isPrepared = event.preparationStatus === 'completed' ||
                        event.preparationStatus === 'completedWithMissing';
      const isReturned = event.returnStatus === 'completed' ||
                        event.returnStatus === 'completedWithMissing';

      if (isPrepared && !isReturned) {
        // Ajouter les équipements directs
        const assignedEquipment = event.assignedEquipment || [];
        assignedEquipment.forEach(eq => equipmentIdsInUse.add(eq.equipmentId));

        // Ajouter les conteneurs
        const assignedContainers = event.assignedContainers || [];
        assignedContainers.forEach(containerId => containerIdsInUse.add(containerId));
      }
    });

    // Récupérer les équipements dans les conteneurs en cours d'utilisation
    if (containerIdsInUse.size > 0) {
      const containersSnapshot = await db.collection('containers')
        .where(admin.firestore.FieldPath.documentId(), 'in', Array.from(containerIdsInUse))
        .get();

      containersSnapshot.docs.forEach(doc => {
        const containerData = doc.data();
        const equipmentList = containerData.equipment || [];
        equipmentList.forEach(eq => equipmentIdsInUse.add(eq.equipmentId));
      });
    }

    // Récupérer les données des équipements demandés
    const statuses = {};

    for (const equipmentId of equipmentIds) {
      const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();

      if (!equipmentDoc.exists) {
        statuses[equipmentId] = null;
        continue;
      }

      const equipmentData = equipmentDoc.data();
      let calculatedStatus = equipmentData.status;

      // Si l'équipement est perdu ou HS, garder ce statut
      if (equipmentData.status === 'lost' || equipmentData.status === 'outOfService') {
        calculatedStatus = equipmentData.status;
      } else if (equipmentIdsInUse.has(equipmentId)) {
        calculatedStatus = 'inUse';
      } else if (equipmentData.status === 'maintenance' ||
                 equipmentData.status === 'rented') {
        calculatedStatus = equipmentData.status;
      } else {
        calculatedStatus = 'available';
      }

      statuses[equipmentId] = calculatedStatus;
    }

    res.status(200).json({ statuses });
  } catch (error) {
    logger.error("Error calculating equipment statuses:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Récupérer tous les événements en cours (pour le calcul de statuts)
 */
exports.getActiveEvents = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_events');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires view_events permission' });
      return;
    }

    // Récupérer les événements en cours (préparation complétée mais pas encore retournés)
    const eventsSnapshot = await db.collection('events')
      .where('status', '!=', 'CANCELLED')
      .get();

    const activeEvents = [];

    eventsSnapshot.docs.forEach(doc => {
      const event = doc.data();

      const isPrepared = event.preparationStatus === 'completed' ||
                        event.preparationStatus === 'completedWithMissing';
      const isReturned = event.returnStatus === 'completed' ||
                        event.returnStatus === 'completedWithMissing';

      if (isPrepared && !isReturned) {
        activeEvents.push({
          id: doc.id,
          assignedEquipment: event.assignedEquipment || [],
          assignedContainers: event.assignedContainers || [],
          preparationStatus: event.preparationStatus,
          returnStatus: event.returnStatus,
        });
      }
    });

    res.status(200).json({ events: activeEvents });
  } catch (error) {
    logger.error("Error fetching active events:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Vérifier les maintenances à venir et créer des alertes
 */
exports.checkUpcomingMaintenances = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const sevenDaysFromNow = new Date();
    sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);

    const now = admin.firestore.Timestamp.now();
    const sevenDaysTimestamp = admin.firestore.Timestamp.fromDate(sevenDaysFromNow);

    // Récupérer les maintenances planifiées dans les 7 prochains jours
    const maintenancesSnapshot = await db.collection('maintenances')
      .where('scheduledDate', '<=', sevenDaysTimestamp)
      .where('scheduledDate', '>=', now)
      .get();

    const alertsCreated = [];

    for (const doc of maintenancesSnapshot.docs) {
      const maintenance = doc.data();

      // Vérifier si une alerte existe déjà pour cette maintenance
      const existingAlertSnapshot = await db.collection('alerts')
        .where('type', '==', 'MAINTENANCE_DUE')
        .where('relatedMaintenanceId', '==', doc.id)
        .get();

      if (existingAlertSnapshot.empty) {
        // Créer une nouvelle alerte
        const alertData = {
          type: 'MAINTENANCE_DUE',
          title: `Maintenance à venir`,
          message: `Une maintenance est prévue pour ${maintenance.equipmentIds?.length || 0} équipement(s)`,
          severity: 'MEDIUM',
          isRead: false,
          relatedMaintenanceId: doc.id,
          createdAt: admin.firestore.Timestamp.now(),
        };

        const alertRef = await db.collection('alerts').add(alertData);
        alertsCreated.push({ id: alertRef.id, ...alertData });
      }
    }

    res.status(200).json({
      success: true,
      alertsCreated: alertsCreated.length,
      alerts: alertsCreated
    });
  } catch (error) {
    logger.error("Error checking upcoming maintenances:", error);
    res.status(500).json({ error: error.message });
  }
}));

/**
 * Compléter une maintenance
 */
exports.completeMaintenance = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');

    if (!hasAccess) {
      res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
      return;
    }

    const { maintenanceId, performedBy, cost } = req.body.data;

    if (!maintenanceId) {
      res.status(400).json({ error: 'maintenanceId is required' });
      return;
    }

    const now = admin.firestore.Timestamp.now();
    const updateData = {
      completedDate: now,
      updatedAt: now,
    };

    if (performedBy) {
      updateData.performedBy = performedBy;
    }

    if (cost !== undefined && cost !== null) {
      updateData.cost = cost;
    }

    // Mettre à jour la maintenance
    await db.collection('maintenances').doc(maintenanceId).update(updateData);

    // Récupérer la maintenance pour mettre à jour les équipements
    const maintenanceDoc = await db.collection('maintenances').doc(maintenanceId).get();
    const maintenanceData = maintenanceDoc.data();

    // Mettre à jour la date de dernière maintenance des équipements
    if (maintenanceData && maintenanceData.equipmentIds) {
      const updatePromises = maintenanceData.equipmentIds.map(equipmentId =>
        db.collection('equipments').doc(equipmentId).update({
          lastMaintenanceDate: now,
          updatedAt: now,
        })
      );

      await Promise.all(updatePromises);
    }

    res.status(200).json({ success: true });
  } catch (error) {
    logger.error("Error completing maintenance:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ==================== EMAIL FUNCTIONS ====================
const {sendAlertEmail} = require('./sendAlertEmail');
exports.sendAlertEmail = sendAlertEmail;

// ==================== ALERT FUNCTIONS ====================
const {createAlert} = require('./createAlert');
exports.createAlert = createAlert;

const {processEquipmentValidation} = require('./processEquipmentValidation');
exports.processEquipmentValidation = processEquipmentValidation;

// ==================== SCHEDULED FUNCTIONS ====================
const {sendDailyDigest} = require('./sendDailyDigest');

/**
 * Fonction schedulée : Envoie quotidien d'un digest des alertes non lues
 * S'exécute tous les jours à 8h00 (Europe/Paris)
 */
exports.sendDailyDigest = onSchedule({
  schedule: '0 8 * * *',
  timeZone: 'Europe/Paris',
  region: 'europe-west9',
  retryCount: 2,
  memory: '512MiB'
}, async (context) => {
  logger.info('[Scheduler] Démarrage sendDailyDigest');
  try {
    await sendDailyDigest();
    logger.info('[Scheduler] sendDailyDigest terminé avec succès');
  } catch (error) {
    logger.error('[Scheduler] Erreur sendDailyDigest:', error);
    throw error;
  }
});

// ==================== FIRESTORE TRIGGERS ====================

/**
 * Trigger : Nouvel événement créé
 * Envoie une notification à tous les membres de la workforce
 */
exports.onEventCreated = onDocumentCreated({
  document: 'events/{eventId}',
  region: 'europe-west9'
}, async (event) => {
  logger.info(`[onEventCreated] Événement créé: ${event.params.eventId}`);

  try {
    const eventData = event.data.data();
    const eventId = event.params.eventId;

    // Créer une alerte pour informer la workforce
    await db.collection('alerts').add({
      type: 'EVENT_CREATED',
      severity: 'INFO',
      message: `Nouvel événement créé : "${eventData.name}" le ${new Date(eventData.startDate?.toDate ? eventData.startDate.toDate() : eventData.startDate).toLocaleDateString('fr-FR')}`,
      eventId: eventId,
      eventName: eventData.name,
      eventDate: eventData.startDate,
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
      isRead: false,
      metadata: {
        eventId: eventId,
        eventName: eventData.name,
        eventDate: eventData.startDate,
      },
      assignedTo: [], // Sera rempli automatiquement par la fonction createAlert
    });

    // Appeler createAlert via HTTP pour gérer l'envoi des emails
    const createAlertModule = require('./createAlert');
    // Note: On ne peut pas appeler directement la fonction HTTP, mais on peut créer l'alerte directement
    // L'envoi des emails sera géré par un trigger sur la collection alerts

    logger.info(`[onEventCreated] Alerte créée pour événement ${eventId}`);
  } catch (error) {
    logger.error('[onEventCreated] Erreur:', error);
  }
});

/**
 * Trigger : Événement modifié (workforce changée)
 * Envoie une notification aux nouveaux membres ajoutés à la workforce
 */
exports.onEventUpdated = onDocumentUpdated({
  document: 'events/{eventId}',
  region: 'europe-west9'
}, async (event) => {
  const before = event.data.before.data();
  const after = event.data.after.data();
  const eventId = event.params.eventId;

  try {
    // Vérifier si la workforce a changé
    const workforceBefore = before.workforce || [];
    const workforceAfter = after.workforce || [];

    // Trouver les nouveaux membres ajoutés
    const newMembers = workforceAfter.filter(afterMember => {
      return !workforceBefore.some(beforeMember =>
        beforeMember.userId === afterMember.userId
      );
    });

    if (newMembers.length > 0) {
      logger.info(`[onEventUpdated] ${newMembers.length} nouveaux membres ajoutés à ${eventId}`);

      // Créer une alerte pour chaque nouveau membre
      for (const member of newMembers) {
        await db.collection('alerts').add({
          type: 'WORKFORCE_ADDED',
          severity: 'INFO',
          message: `Vous avez été ajouté(e) à l'événement "${after.name}" le ${new Date(after.startDate?.toDate ? after.startDate.toDate() : after.startDate).toLocaleDateString('fr-FR')}`,
          eventId: eventId,
          eventName: after.name,
          eventDate: after.startDate,
          createdAt: admin.firestore.FieldValue.serverTimestamp(),
          isRead: false,
          metadata: {
            eventId: eventId,
            eventName: after.name,
            eventDate: after.startDate,
          },
          assignedTo: [member.userId], // Alerte ciblée uniquement pour ce membre
        });

        logger.info(`[onEventUpdated] Alerte créée pour ${member.userId}`);
      }
    }
  } catch (error) {
    logger.error('[onEventUpdated] Erreur:', error);
  }
});

/**
 * Trigger : Nouvelle alerte créée
 * Envoie un email immédiat si l'alerte est critique
 */
exports.onAlertCreated = onDocumentCreated({
  document: 'alerts/{alertId}',
  region: 'europe-west9'
}, async (event) => {
  const alertId = event.params.alertId;
  const alertData = event.data.data();

  logger.info(`[onAlertCreated] Nouvelle alerte: ${alertId} (${alertData.severity})`);

  try {
    // Si l'alerte est critique et pas encore envoyée par email
    if (alertData.severity === 'CRITICAL' && !alertData.emailSent) {
      const sendEmailModule = require('./sendAlertEmail');

      // Les destinataires sont déjà dans assignedTo
      const userIds = alertData.assignedTo || [];

      if (userIds.length > 0) {
        logger.info(`[onAlertCreated] Envoi email immédiat à ${userIds.length} utilisateurs`);

        // Note: Dans un trigger Firestore, on ne peut pas facilement appeler une fonction HTTP
        // Il faudrait soit:
        // 1. Dupliquer la logique d'envoi d'email ici
        // 2. Utiliser une file d'attente (Pub/Sub ou Tasks)
        // 3. Marquer l'alerte pour qu'elle soit traitée par un scheduler

        // Pour l'instant, on marque l'alerte comme devant être envoyée
        await db.collection('alerts').doc(alertId).update({
          pendingEmailSend: true,
        });

        logger.info(`[onAlertCreated] Alerte marquée pour envoi email`);
      }
    }
  } catch (error) {
    logger.error('[onAlertCreated] Erreur:', error);
  }
});

// ==================== ALERT TRIGGERS ====================
// Temporairement désactivé - erreur de permissions Eventarc
// const {onAlertCreated} = require('./onAlertCreated');
// exports.onAlertCreated = onAlertCreated;

// ============================================================================
// EQUIPMENTS - Pagination et filtrage avancé
// ============================================================================

/**
 * Récupère les équipements avec pagination et filtrage côté serveur
 *
 * Paramètres de requête supportés:
 * - limit: nombre d'éléments par page (défaut: 20, max: 100)
 * - startAfter: ID du dernier élément de la page précédente (pour pagination)
 * - category: filtre par catégorie
 * - status: filtre par statut
 * - searchQuery: recherche textuelle (nom, ID, modèle, marque)
 * - sortBy: champ de tri (défaut: 'id')
 * - sortOrder: 'asc' ou 'desc' (défaut: 'asc')
 */
exports.getEquipmentsPaginated = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    // Vérifier les permissions
    const canManage = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
    const canView = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!canManage && !canView) {
      res.status(403).json({ error: 'Forbidden: Requires equipment permissions' });
      return;
    }

    // Récupérer les paramètres de la requête
    const params = req.method === 'GET' ? req.query : (req.body?.data || {});
    const limit = Math.min(parseInt(params.limit) || 20, 100);
    const startAfterId = params.startAfter || null;
    // Convertir en majuscules pour correspondre au format Firestore
    const category = params.category ? params.category.toUpperCase() : null;
    const status = params.status ? params.status.toUpperCase() : null;
    const searchQuery = params.searchQuery?.toLowerCase() || null;
    const sortBy = params.sortBy || 'id';
    const sortOrder = params.sortOrder === 'desc' ? 'desc' : 'asc';

    logger.info(`[getEquipmentsPaginated] Params: limit=${limit}, startAfter=${startAfterId}, category=${category}, status=${status}, search=${searchQuery}`);

    // Construire la requête Firestore
    let query = db.collection('equipments');

    // Si recherche textuelle, on augmente la limite pour filtrer ensuite
    const queryLimit = searchQuery ? Math.min(limit * 10, 200) : limit;

    // Appliquer les filtres
    if (category) {
      query = query.where('category', '==', category);
    }
    if (status) {
      query = query.where('status', '==', status);
    }

    // Tri : Utiliser FieldPath.documentId() pour trier par l'UID du document
    // Cela garantit que TOUS les documents sont inclus, même sans champ 'id'
    if (sortBy === 'id') {
      query = query.orderBy(admin.firestore.FieldPath.documentId(), sortOrder);
    } else {
      query = query.orderBy(sortBy, sortOrder);
    }

    // Pagination
    if (startAfterId) {
      const startAfterDoc = await db.collection('equipments').doc(startAfterId).get();
      if (startAfterDoc.exists) {
        query = query.startAfter(startAfterDoc);
      }
    }

    // Limiter les résultats
    query = query.limit(queryLimit + 1);

    const snapshot = await query.get();

    // Déterminer hasMore basé sur le nombre de documents Firestore
    const rawDocCount = snapshot.docs.length;
    const hasMoreDocs = rawDocCount > queryLimit;
    const docsToProcess = hasMoreDocs ? snapshot.docs.slice(0, queryLimit) : snapshot.docs;

    logger.info(`[getEquipmentsPaginated] Firestore returned ${rawDocCount} docs, hasMore=${hasMoreDocs}`);

    let equipments = docsToProcess.map(doc => {
      const data = doc.data();

      // Masquer les prix si l'utilisateur n'a pas manage_equipment
      if (!canManage) {
        delete data.purchasePrice;
        delete data.rentalPrice;
      }

      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data, ['purchaseDate', 'nextMaintenanceDate', 'lastMaintenanceDate', 'createdAt', 'updatedAt'])
      };
    });

    // Filtrage textuel côté serveur
    if (searchQuery) {
      equipments = equipments.filter(eq => {
        const searchableText = [
          eq.name || '',
          eq.id || '',
          eq.model || '',
          eq.brand || '',
          eq.subCategory || ''
        ].join(' ').toLowerCase();
        return searchableText.includes(searchQuery);
      });
    }

    // Pour la limite finale après filtrage textuel
    const limitedEquipments = equipments.slice(0, limit);
    const lastVisible = limitedEquipments.length > 0 ? limitedEquipments[limitedEquipments.length - 1].id : null;

    // hasMore reste basé sur le nombre de docs Firestore, pas sur le filtrage textuel
    logger.info(`[getEquipmentsPaginated] Returning ${limitedEquipments.length} equipments (filtered from ${equipments.length}), hasMore=${hasMoreDocs}`);

    res.status(200).json({
      equipments: limitedEquipments,
      hasMore: hasMoreDocs,
      lastVisible,
      total: limitedEquipments.length
    });

  } catch (error) {
    logger.error("Error fetching paginated equipments:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// CONTAINERS - Pagination et filtrage avancé
// ============================================================================

/**
 * Récupère les containers avec pagination et filtrage côté serveur
 *
 * Paramètres similaires à getEquipmentsPaginated
 */
exports.getContainersPaginated = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    // Vérifier les permissions
    const canView = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!canView) {
      res.status(403).json({ error: 'Forbidden: Requires equipment permissions' });
      return;
    }

    // Récupérer les paramètres de la requête
    const params = req.method === 'GET' ? req.query : (req.body?.data || {});
    const limit = Math.min(parseInt(params.limit) || 20, 100);
    const startAfterId = params.startAfter || null;
    // Convertir en majuscules pour correspondre au format Firestore
    const type = params.type ? params.type.toUpperCase() : null;
    const status = params.status ? params.status.toUpperCase() : null;
    const searchQuery = params.searchQuery?.toLowerCase() || null;
    const category = params.category ? params.category.toUpperCase() : null; // Filtre par catégorie d'équipements
    const sortBy = params.sortBy || 'id';
    const sortOrder = params.sortOrder === 'desc' ? 'desc' : 'asc';

    logger.info(`[getContainersPaginated] Params: limit=${limit}, startAfter=${startAfterId}, type=${type}, status=${status}, category=${category}, search=${searchQuery}`);

    // Construire la requête Firestore
    let query = db.collection('containers');

    // Si recherche textuelle ou filtre par catégorie, on augmente la limite pour filtrer ensuite
    const queryLimit = (searchQuery || category) ? Math.min(limit * 10, 200) : limit;

    // Appliquer les filtres sur les containers
    if (type) {
      query = query.where('type', '==', type);
    }
    if (status) {
      query = query.where('status', '==', status);
    }

    // Tri : Utiliser FieldPath.documentId() pour trier par l'UID du document
    if (sortBy === 'id') {
      query = query.orderBy(admin.firestore.FieldPath.documentId(), sortOrder);
    } else {
      query = query.orderBy(sortBy, sortOrder);
    }

    // Pagination
    if (startAfterId) {
      const startAfterDoc = await db.collection('containers').doc(startAfterId).get();
      if (startAfterDoc.exists) {
        query = query.startAfter(startAfterDoc);
      }
    }

    // Limiter les résultats
    query = query.limit(queryLimit + 1);

    const snapshot = await query.get();

    // Déterminer hasMore basé sur le nombre de documents Firestore
    const rawDocCount = snapshot.docs.length;
    const hasMoreDocs = rawDocCount > queryLimit;
    const docsToProcess = hasMoreDocs ? snapshot.docs.slice(0, queryLimit) : snapshot.docs;

    let containers = docsToProcess.map(doc => {
      const data = doc.data();
      return {
        id: doc.id,
        ...helpers.serializeTimestamps(data, ['createdAt', 'updatedAt'])
      };
    });

    // Récupérer tous les équipements liés aux containers (pour population ET filtrage)
    const allEquipmentIds = new Set();
    containers.forEach(c => {
      if (c.equipmentIds && Array.isArray(c.equipmentIds)) {
        c.equipmentIds.forEach(id => allEquipmentIds.add(id));
      }
    });

    // Charger les équipements en batch (max 30 par requête Firestore)
    const equipmentMap = new Map();
    if (allEquipmentIds.size > 0) {
      const equipmentIdArray = Array.from(allEquipmentIds);
      const batchSize = 30; // Limite Firestore pour les requêtes 'in'

      for (let i = 0; i < equipmentIdArray.length; i += batchSize) {
        const batch = equipmentIdArray.slice(i, i + batchSize);
        const equipmentSnapshot = await db.collection('equipments')
          .where(admin.firestore.FieldPath.documentId(), 'in', batch)
          .get();

        equipmentSnapshot.docs.forEach(doc => {
          const equipmentData = doc.data();
          equipmentMap.set(doc.id, {
            id: doc.id,
            ...helpers.serializeTimestamps(equipmentData)
          });
        });
      }
    }

    // Peupler les containers avec leurs équipements
    containers = containers.map(container => ({
      ...container,
      equipment: (container.equipmentIds || [])
        .map(eqId => equipmentMap.get(eqId))
        .filter(eq => eq !== undefined) // Retirer les équipements non trouvés
    }));

    // Filtrage par catégorie d'équipements
    if (category) {
      containers = containers.filter(c => {
        // Garder le container s'il contient au moins un équipement de la catégorie demandée
        return c.equipment.some(eq => eq.category === category);
      });
    }

    // Filtrage textuel côté serveur
    if (searchQuery) {
      containers = containers.filter(c => {
        const searchableText = [
          c.name || '',
          c.id || '',
          ...(c.equipment || []).map(eq => eq.name || '')
        ].join(' ').toLowerCase();
        return searchableText.includes(searchQuery);
      });
    }

    // Pour la limite finale après filtrage
    const limitedContainers = containers.slice(0, limit);
    const lastVisible = limitedContainers.length > 0 ? limitedContainers[limitedContainers.length - 1].id : null;

    // Log pour debugging
    const totalEquipmentCount = limitedContainers.reduce((sum, c) => sum + (c.equipment?.length || 0), 0);
    logger.info(`[getContainersPaginated] Returning ${limitedContainers.length} containers with ${totalEquipmentCount} total equipment(s)`);

    // Log détaillé pour chaque container
    limitedContainers.forEach(c => {
      logger.info(`  - Container ${c.id}: ${c.equipment?.length || 0} equipment(s)`);
    });

    res.status(200).json({
      containers: limitedContainers,
      hasMore: containers.length > limit || hasMoreDocs,
      lastVisible,
      total: limitedContainers.length
    });

  } catch (error) {
    logger.error("Error fetching paginated containers:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// SEARCH - Recherche unifiée avec autocomplétion
// ============================================================================

/**
 * Recherche rapide d'équipements et containers pour l'autocomplétion
 * Retourne un nombre limité de résultats pour des performances optimales
 */
exports.quickSearch = onRequest(httpOptions, withCors(async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);

    // Vérifier les permissions
    const canView = await auth.hasPermission(decodedToken.uid, 'view_equipment');

    if (!canView) {
      res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
      return;
    }

    const params = req.method === 'GET' ? req.query : (req.body?.data || {});
    const searchQuery = params.query?.toLowerCase() || '';
    const limit = Math.min(parseInt(params.limit) || 10, 50);
    const includeEquipments = params.includeEquipments !== 'false';
    const includeContainers = params.includeContainers !== 'false';

    if (!searchQuery || searchQuery.length < 2) {
      res.status(200).json({ results: [] });
      return;
    }

    const results = [];

    // Rechercher dans les équipements
    if (includeEquipments) {
      const equipmentSnapshot = await db.collection('equipments')
        .orderBy('id')
        .limit(limit * 2) // Récupérer plus pour filtrer ensuite
        .get();

      equipmentSnapshot.docs.forEach(doc => {
        const data = doc.data();
        const searchableText = [
          data.name || '',
          doc.id || '',
          data.model || '',
          data.brand || ''
        ].join(' ').toLowerCase();

        if (searchableText.includes(searchQuery)) {
          results.push({
            type: 'equipment',
            id: doc.id,
            name: data.name,
            category: data.category,
            model: data.model,
            brand: data.brand
          });
        }
      });
    }

    // Rechercher dans les containers
    if (includeContainers) {
      const containerSnapshot = await db.collection('containers')
        .orderBy('id')
        .limit(limit * 2)
        .get();

      containerSnapshot.docs.forEach(doc => {
        const data = doc.data();
        const searchableText = [
          data.name || '',
          doc.id || ''
        ].join(' ').toLowerCase();

        if (searchableText.includes(searchQuery)) {
          results.push({
            type: 'container',
            id: doc.id,
            name: data.name,
            containerType: data.type
          });
        }
      });
    }

    // Limiter et trier les résultats
    const limitedResults = results
      .sort((a, b) => {
        // Prioriser les correspondances exactes au début
        const aStarts = a.id.toLowerCase().startsWith(searchQuery);
        const bStarts = b.id.toLowerCase().startsWith(searchQuery);
        if (aStarts && !bStarts) return -1;
        if (!aStarts && bStarts) return 1;
        return 0;
      })
      .slice(0, limit);

    res.status(200).json({ results: limitedResults });

  } catch (error) {
    logger.error("Error in quick search:", error);
    res.status(500).json({ error: error.message });
  }
}));

// ============================================================================
// TEXT-TO-SPEECH - Generate TTS Audio
// ============================================================================
// Options HTTP spécifiques pour TTS avec CORS activé
const ttsHttpOptions = {
  cors: true, // Activer CORS automatique
  invoker: 'public',
  region: 'europe-west9',
};

exports.generateTTSV2 = onRequest(ttsHttpOptions, async (req, res) => {
  try {
    // Authentification utilisateur
    const decodedToken = await auth.authenticateUser(req);

    logger.info('[generateTTSV2] Request from user:', {
      uid: decodedToken.uid,
      email: decodedToken.email,
    });

    // Récupération des paramètres
    const { text, voiceConfig } = req.body.data || {};

    // Validation
    if (!text) {
      res.status(400).json({ error: 'Text parameter is required' });
      return;
    }

    if (text.length > 5000) {
      res.status(400).json({ error: 'Text too long (max 5000 characters)' });
      return;
    }

    // Génération de l'audio avec cache
    const bucketName = admin.storage().bucket().name;
    const bucket = storage.bucket(bucketName);

    const result = await generateTTS(text, storage, bucket, voiceConfig);

    logger.info('[generateTTSV2] ✓ Success', {
      cached: result.cached,
      cacheKey: result.cacheKey,
    });

    res.status(200).json({
      audioUrl: result.audioUrl,
      cached: result.cached,
      cacheKey: result.cacheKey,
    });

  } catch (error) {
    logger.error('[generateTTSV2] ✗ Error:', {
      error: error.message,
      code: error.code,
    });

    // Gestion des erreurs spécifiques
    if (error.code === 'PERMISSION_DENIED') {
      res.status(403).json({ error: 'Permission denied. Check Google Cloud TTS API is enabled.' });
    } else if (error.code === 'QUOTA_EXCEEDED') {
      res.status(429).json({ error: 'TTS quota exceeded. Try again later.' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

