const admin = require("firebase-admin");
const db = admin.firestore();
const logger = require("firebase-functions/logger");
const auth = require("../utils/auth");
const helpers = require("../utils/helpers");

// Vérifie si un équipement est disponible pour une plage de dates
exports.checkEquipmentAvailability = 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;
      }

      let eventStart; let 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;
      }

      const assignedEquipment = event.assignedEquipment || [];
      const assignedContainers = event.assignedContainers || [];

      const isEquipmentDirectlyAssigned = assignedEquipment.some((eq) => eq.equipmentId === equipmentId);

      let isEquipmentInAssignedContainer = false;
      if (assignedContainers.length > 0) {
        logger.info(`Event ${eventDoc.id} has ${assignedContainers.length} assigned containers`);
        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;
      }

      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));

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

        const eventData = helpers.serializeTimestamps(event);
        conflicts.push({
          eventId: eventDoc.id,
          eventName: event.Name,
          eventData: eventData,
          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"});
  }
};

// Vérifie la disponibilité d'un container et de son contenu
exports.checkContainerAvailability = 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;
    }

    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;
      }

      let eventStart; let 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;
      }

      const assignedContainers = event.assignedContainers || [];
      const isContainerAssigned = assignedContainers.includes(containerId);

      const assignedEquipment = event.assignedEquipment || [];
      const conflictingEquipmentIds = equipmentIds.filter((eqId) =>
        assignedEquipment.some((eq) => eq.equipmentId === eqId),
      );

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

      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"});
  }
};

// Récupère tous les équipements et conteneurs en conflit pour une période donnée
exports.getConflictingEquipmentIds = 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}`);

    const requestStartDate = new Date(startDate);
    requestStartDate.setHours(requestStartDate.getHours() - installationTime);

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

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

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

    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",
      };
    });

    const conflictingEquipmentIds = new Set();
    const conflictingContainerIds = new Set();
    const conflictDetails = {};
    const equipmentQuantities = {};

    for (const eventDoc of eventsSnapshot.docs) {
      if (excludeEventId && eventDoc.id === excludeEventId) {
        continue;
      }

      const event = eventDoc.data();

      let eventStart; let 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;
      }

      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);

      const hasOverlap = requestStartDate < eventEndWithTeardown && requestEndDate > eventStartWithSetup;

      if (!hasOverlap) {
        continue;
      }

      const assignedEquipment = event.assignedEquipment || [];
      const assignedContainers = event.assignedContainers || [];

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

      for (const eq of assignedEquipment) {
        const equipmentId = eq.equipmentId;
        const quantity = eq.quantity || 1;
        const equipInfo = equipmentsInfo[equipmentId];

        if (equipInfo && equipInfo.hasQuantity) {
          if (!equipmentQuantities[equipmentId]) {
            equipmentQuantities[equipmentId] = {
              totalQuantity: equipInfo.totalQuantity,
              reservedQuantity: 0,
              availableQuantity: equipInfo.totalQuantity,
              reservations: [],
            };
          }

          equipmentQuantities[equipmentId].reservedQuantity += quantity;
          equipmentQuantities[equipmentId].availableQuantity = equipInfo.totalQuantity - equipmentQuantities[equipmentId].reservedQuantity;
          equipmentQuantities[equipmentId].reservations.push({
            ...conflictInfo,
            quantity: quantity,
          });

          if (equipmentQuantities[equipmentId].availableQuantity <= 0) {
            conflictingEquipmentIds.add(equipmentId);
          }
        } else {
          conflictingEquipmentIds.add(equipmentId);
        }

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

      for (const containerId of assignedContainers) {
        conflictingContainerIds.add(containerId);

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

        const containerDoc = await db.collection("containers").doc(containerId).get();
        if (containerDoc.exists) {
          const containerData = containerDoc.data();
          const equipmentIds = containerData.equipmentIds || [];

          for (const equipmentId of equipmentIds) {
            conflictingEquipmentIds.add(equipmentId);

            if (!conflictDetails[equipmentId]) {
              conflictDetails[equipmentId] = [];
            }
            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,
    });
  } catch (error) {
    logger.error("Error getting conflicting equipment IDs:", error);
    res.status(500).json({error: error.message || "Failed to get conflicting equipment IDs"});
  }
};

/**
 * Trouver des alternatives (même modèle) disponibles pour une période donnée
 */
exports.findAlternativeEquipment = 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 = 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 === "OUT_OF_SERVICE") {
        calculatedStatus = equipmentData.status;
      } else if (equipmentIdsInUse.has(equipmentId)) {
        calculatedStatus = "IN_USE";
      } 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 = 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});
  }
};
