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

// Helper functions for search
const normalizeSearchText = (value) => {
  return (value || "")
      .toString()
      .toLowerCase()
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "")
      .trim();
};

const getEventStartDate = (eventData) => {
  const startValue = eventData.StartDateTime;

  if (!startValue) {
    return null;
  }

  if (startValue.toDate) {
    return startValue.toDate();
  }

  const parsedDate = new Date(startValue);
  return Number.isNaN(parsedDate.getTime()) ? null : parsedDate;
};

const getEventWorkforceUids = (eventData) => {
  if (!eventData.workforce || !Array.isArray(eventData.workforce)) {
    return [];
  }

  return eventData.workforce
      .map((userRef) => {
        if (userRef && userRef.id) {
          return userRef.id;
        }

        if (typeof userRef === "string" && userRef.startsWith("users/")) {
          return userRef.split("/")[1];
        }

        return null;
      })
      .filter((uid) => uid !== null);
};

const serializeEventSearchResult = (doc) => {
  const data = doc.data();

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

// Créer un événement
exports.createEvent = 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;

    let dataToSave = helpers.deserializeTimestamps(eventData, [
      "StartDateTime", "EndDateTime", "createdAt", "updatedAt",
    ]);

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

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

    let dataToSave = helpers.deserializeTimestamps(data, [
      "StartDateTime", "EndDateTime",
    ]);

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

// Met à jour les équipements d'un événement
exports.updateEventEquipment = 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;
    }

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

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

    const updateData = {};

    if (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;

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

// Récupérer les événements utilisant un type d'événement
exports.getEventsByEventType = 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});
  }
};

// Récupère tous les événements (filtrés selon permissions)
exports.getEvents = async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const {userId} = req.body.data || {};

    const canViewAll = await auth.hasPermission(decodedToken.uid, "view_all_events");

    let eventsSnapshot;

    if (canViewAll) {
      eventsSnapshot = await db.collection("events").get();
    } else {
      const userRef = db.collection("users").doc(userId || decodedToken.uid);
      eventsSnapshot = await db.collection("events")
          .where("workforce", "array-contains", userRef)
          .get();
    }

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

    const usersMap = {};
    if (userIdsSet.size > 0) {
      const userIds = Array.from(userIdsSet);
      const batchSize = 30;

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

    const events = eventsSnapshot.docs.map((doc) => {
      const data = doc.data();

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

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

// Récupère les événements d'un mois spécifique (lazy loading optimisé)
exports.getEventsByMonth = 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}`);

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

    const canViewAll = await auth.hasPermission(decodedToken.uid, "view_all_events");

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

    if (!canViewAll) {
      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}`);

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

    const usersMap = {};
    if (userIdsSet.size > 0) {
      const userIds = Array.from(userIdsSet);
      const batchSize = 30;

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

    const events = eventsSnapshot.docs.map((doc) => {
      const data = doc.data();

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

// Recherche des événements accessibles à l'utilisateur
exports.searchEvents = async (req, res) => {
  try {
    const decodedToken = await auth.authenticateUser(req);
    const {userId, query, limit = 20} = req.body.data || {};
    const maxResults = Number.isFinite(Number(limit)) ? Math.max(1, Number(limit)) : 20;

    const normalizedQuery = normalizeSearchText(query);
    if (!normalizedQuery) {
      res.status(200).json({events: []});
      return;
    }

    const canViewAll = await auth.hasPermission(decodedToken.uid, "view_all_events");

    let eventsSnapshot;
    if (canViewAll) {
      eventsSnapshot = await db.collection("events").get();
    } else {
      const userRef = db.collection("users").doc(userId || decodedToken.uid);
      eventsSnapshot = await db.collection("events")
          .where("workforce", "array-contains", userRef)
          .get();
    }

    const matchingEvents = eventsSnapshot.docs
        .filter((doc) => {
          const eventData = doc.data();
          const startDate = getEventStartDate(eventData);
          const searchableText = normalizeSearchText([
            eventData.Name,
            eventData.Description,
            eventData.Address,
          startDate ? startDate.toLocaleString("fr-FR") : "",
          startDate ? startDate.toISOString() : "",
          ].join(" "));

          return searchableText.includes(normalizedQuery);
        })
        .sort((a, b) => {
          const startA = getEventStartDate(a.data()) || new Date(0);
          const startB = getEventStartDate(b.data()) || new Date(0);
          return startA.getTime() - startB.getTime();
        })
        .slice(0, maxResults)
        .map((doc) => serializeEventSearchResult(doc));

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

// Récupère un événement avec tous les détails (équipements complets + containers avec enfants)
exports.getEventWithDetails = 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;
    }

    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 canViewAll = await auth.hasPermission(decodedToken.uid, "view_all_events");
    if (!canViewAll) {
      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}`);

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

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

    const containerPromises = Array.from(containerIds).map((id) =>
      db.collection("containers").doc(id).get(),
    );
    const containerDocs = await Promise.all(containerPromises);

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

    const childEquipmentPromises = Array.from(childEquipmentIds).map((id) =>
      db.collection("equipments").doc(id).get(),
    );
    const childEquipmentDocs = await Promise.all(childEquipmentPromises);

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

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

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

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



// Valider un équipement individuel pour le chargement
exports.validateEquipmentLoading = 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 = 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 = 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 = 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});
  }
};


