const { onRequest, onCall, HttpsError } = require("firebase-functions/v2/https");
const { getAuth } = require("firebase-admin/auth");
const { getFirestore, Timestamp, FieldValue, FieldPath } = require("firebase-admin/firestore");
const logger = require("firebase-functions/logger");
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const fetch = require("node-fetch");
const { Expo } = require("expo-server-sdk");

admin.initializeApp();
const db = admin.firestore();

let expo = new Expo({ accessToken: process.env.EXPO_ACCESS_TOKEN });

const GOOGLE_CALENDAR_ID = "primary";
const CHANNEL_ID = "cally-family-calendar";
const WEBHOOK_URL = "https://sendsyncnotification-ne4kpcdqwa-uc.a.run.app";

/* ─────────────────────────────────
   PUSH NOTIFICATION HELPERS
───────────────────────────────── */
async function getPushTokensForFamily(familyId, excludeUserId = null) {
    const snapshot = await db.collection("Profiles")
        .where("familyId", "==", familyId)
        .get();

    let pushTokens = [];
    logger.info("Getting push tokens", { familyId, excludeUserId });
    snapshot.forEach(doc => {
        const data = doc.data();
        const userId = doc.id;
        logger.debug("Processing user", { docId: userId, hasToken: !!data.pushToken, excluded: userId === excludeUserId });
        if (userId !== excludeUserId && data.pushToken) {
            logger.info("Including token for user", { userId, excludeUserId });
            pushTokens.push(data.pushToken);
        } else {
            logger.debug("Excluding token for user", { userId, excludeUserId });
        }
    });
    return pushTokens;
}

async function removeInvalidPushToken(pushToken) {
    try {
        const snapshot = await db.collection("Profiles")
            .where("pushToken", "==", pushToken)
            .get();
        const batch = db.batch();
        snapshot.forEach(doc => batch.update(doc.ref, { pushToken: FieldValue.delete() }));
        await batch.commit();
        logger.info(`Removed invalid push token: ${pushToken}`);
    } catch (error) {
        logger.error("Error removing invalid push token", error);
    }
}

async function sendNotifications(pushTokens, notification) {
    if (!pushTokens.length) return;
    const messages = pushTokens
        .filter(token => Expo.isExpoPushToken(token))
        .map(token => ({
            to: token,
            sound: "default",
            priority: "high",
            ...notification,
        }));
    const chunks = expo.chunkPushNotifications(messages);
    for (let chunk of chunks) {
        try {
            const tickets = await expo.sendPushNotificationsAsync(chunk);
            tickets.forEach(ticket => {
                if (ticket.status === "error") {
                    if (ticket.details?.error === "DeviceNotRegistered") {
                        removeInvalidPushToken(ticket.to);
                    }
                    logger.error("Push notification error", ticket.message);
                }
            });
        } catch (error) {
            logger.error("Error sending notifications", error);
        }
    }
}

async function storeNotification(notificationData) {
    try {
        await db.collection("Notifications").add(notificationData);
    } catch (error) {
        logger.error("Error storing notification", error);
    }
}

/* ─────────────────────────────────
   TOKEN REFRESH HELPERS
───────────────────────────────── */
async function refreshGoogleToken(refreshToken) {
    try {
        logger.info("Refreshing Google token...");
        const response = await fetch("https://oauth2.googleapis.com/token", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                grant_type: "refresh_token",
                refresh_token: refreshToken,
                client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
            }),
        });
        if (!response.ok) {
            const errorData = await response.json();
            logger.error("Error refreshing Google token", errorData);
            throw new Error(`Failed to refresh Google token: ${errorData.error || response.statusText}`);
        }
        const data = await response.json();
        logger.info("Google token refreshed successfully");
        return {
            refreshedGoogleToken: data.access_token,
            refreshedRefreshToken: data.refresh_token || refreshToken,
        };
    } catch (error) {
        logger.error("Error refreshing Google token", error.message);
        throw error;
    }
}

async function refreshMicrosoftToken(refreshToken) {
    try {
        logger.info("Refreshing Microsoft token...");
        const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({
                client_id: "13c79071-1066-40a9-9f71-b8c4b138b4af",
                scope: "openid profile email offline_access Calendars.ReadWrite User.Read",
                refresh_token: refreshToken,
                grant_type: "refresh_token",
            }),
        });
        if (!response.ok) {
            const errorData = await response.json();
            logger.error("Error refreshing Microsoft token", errorData);
            throw new Error(`Failed to refresh Microsoft token: ${errorData.error || response.statusText}`);
        }
        const data = await response.json();
        logger.info("Microsoft token refreshed successfully");
        return { accessToken: data.access_token, refreshToken: data.refresh_token || refreshToken };
    } catch (error) {
        logger.error("Error refreshing Microsoft token", error.message);
        throw error;
    }
}

/* ─────────────────────────────────
   GOOGLE EVENT SYNC / STORAGE HELPERS
───────────────────────────────── */
async function saveEventsToFirestore(events) {
    const batch = db.batch();
    events.forEach(event => {
        const eventRef = db.collection("Events").doc(event.id);
        batch.set(eventRef, event, { merge: true });
    });
    await batch.commit();
}

async function fetchAndSaveGoogleEvents({ token, refreshToken, email, familyId, creatorId }) {
    const baseDate = new Date();
    const oneYearAgo = new Date(baseDate);
    oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
    const oneYearAhead = new Date(baseDate);
    oneYearAhead.setFullYear(oneYearAhead.getFullYear() + 1);
    let totalEvents = 0, pageToken = null, batchSize = 250;
    try {
        logger.info(`[FETCH] Starting event fetch for user: ${email}`);
        do {
            const url = new URL("https://www.googleapis.com/calendar/v3/calendars/primary/events");
            url.searchParams.set("singleEvents", "true");
            url.searchParams.set("timeMin", oneYearAgo.toISOString());
            url.searchParams.set("timeMax", oneYearAhead.toISOString());
            url.searchParams.set("maxResults", batchSize.toString());
            if (pageToken) url.searchParams.set("pageToken", pageToken);
            let response = await fetch(url.toString(), {
                headers: { Authorization: `Bearer ${token}` },
            });
            if (response.status === 401 && refreshToken) {
                logger.info(`[TOKEN] Token expired during fetch, refreshing for ${email}`);
                const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
                token = refreshedGoogleToken;
                await db.collection("Profiles").doc(creatorId).update({
                    [`googleAccounts.${email}.accessToken`]: refreshedGoogleToken,
                });
                response = await fetch(url.toString(), {
                    headers: { Authorization: `Bearer ${refreshedGoogleToken}` },
                });
            }
            const data = await response.json();
            if (!response.ok) {
                throw new Error(`Error fetching events: ${data.error?.message || response.statusText}`);
            }
            const events = (data.items || []).map(item => ({
                id: item.id,
                title: item.summary || "",
                startDate: item.start?.dateTime ? new Date(item.start.dateTime) : new Date(item.start.date + "T00:00:00"),
                endDate: item.end?.dateTime
                    ? new Date(item.end.dateTime)
                    : new Date(new Date(item.end.date + "T00:00:00").setDate(new Date(item.end.date).getDate() - 1)),
                allDay: !item.start?.dateTime,
                familyId,
                email,
                creatorId,
                externalOrigin: "google",
                private: item.visibility === "private" || item.visibility === "confidential",
            }));
            if (events.length > 0) {
                logger.info(`[FETCH] Saving batch of ${events.length} events`);
                await saveEventsToFirestore(events);
                totalEvents += events.length;
            }
            pageToken = data.nextPageToken;
        } while (pageToken);
        logger.info(`[FETCH] Completed with ${totalEvents} total events`);
        return totalEvents;
    } catch (error) {
        logger.error(`[ERROR] Failed fetching events for ${email}`, error);
        throw error;
    }
}

async function calendarSync({ userId, email, token, refreshToken, familyId }) {
    logger.info(`[SYNC] Starting calendar sync for user ${userId} with email ${email}`);
    try {
        if (refreshToken) {
            logger.info(`[TOKEN] Initial token refresh for ${email}`);
            const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
            if (refreshedGoogleToken) {
                token = refreshedGoogleToken;
                await db.collection("Profiles").doc(userId).update({
                    [`googleAccounts.${email}.accessToken`]: refreshedGoogleToken,
                });
            }
        }
        const eventCount = await fetchAndSaveGoogleEvents({ token, refreshToken, email, familyId, creatorId: userId });
        logger.info(`[SYNC] Calendar sync completed. Processed ${eventCount} events`);
        return eventCount;
    } catch (error) {
        logger.error(`[ERROR] Calendar sync failed for user ${userId}`, error);
        throw error;
    }
}

/* ─────────────────────────────────
   MICROSOFT EVENT SYNC HELPERS
───────────────────────────────── */
async function fetchAndSaveMicrosoftEvents({ token, refreshToken, email, familyId, creatorId }) {
    const baseDate = new Date();
    // For Microsoft, we fetch events from one month ago for two months ahead
    const timeMin = new Date(baseDate.setMonth(baseDate.getMonth() - 1)).toISOString();
    const timeMax = new Date(baseDate.setMonth(baseDate.getMonth() + 2)).toISOString();
    try {
        logger.info(`Fetching Microsoft events for user: ${email}`);
        const url = `https://graph.microsoft.com/v1.0/me/calendar/events`;
        const queryParams = new URLSearchParams({
            $select: "subject,start,end,id,isAllDay,sensitivity",
            $filter: `start/dateTime ge '${timeMin}' and end/dateTime le '${timeMax}'`,
        });
        let response = await fetch(`${url}?${queryParams}`, {
            headers: {
                Authorization: `Bearer ${token}`,
                "Content-Type": "application/json",
            },
        });
        let data = await response.json();
        if (response.status === 401 && refreshToken) {
            logger.info(`Token expired for Microsoft for user: ${email}, refreshing...`);
            const { accessToken: newToken } = await refreshMicrosoftToken(refreshToken);
            token = newToken;
            return await fetchAndSaveMicrosoftEvents({ token, refreshToken, email, familyId, creatorId });
        }
        if (!response.ok) {
            throw new Error(`Error fetching events: ${data.error?.message || response.statusText}`);
        }
        const events = data.value.map(item => {
            let startDate, endDate;
            if (item.isAllDay) {
                startDate = new Date(item.start.date + "T00:00:00");
                endDate = new Date(new Date(item.end.date + "T00:00:00").setDate(new Date(item.end.date).getDate() - 1));
            } else {
                startDate = new Date(item.start.dateTime + "Z");
                endDate = new Date(item.end.dateTime + "Z");
            }
            return {
                id: item.id,
                title: item.subject || "",
                startDate,
                endDate,
                allDay: item.isAllDay,
                familyId,
                email,
                creatorId,
                externalOrigin: "microsoft",
                private: item.sensitivity === "private" || item.sensitivity === "confidential",
            };
        });
        logger.info(`Saving ${events.length} Microsoft events for user: ${email}`);
        await saveEventsToFirestore(events);
        return events.length;
    } catch (error) {
        logger.error(`Error fetching Microsoft events for ${email}`, error);
        throw error;
    }
}

exports.sendOverviews = functions.pubsub
    .schedule('0 20 * * *')  // Runs at 8 PM daily
    .onRun(async (context) => {
        const familiesSnapshot = await admin.firestore().collection('Families').get();

        for (const familyDoc of familiesSnapshot.docs) {
            const familyId = familyDoc.id;
            const familySettings = familyDoc.data()?.settings || {};
            const overviewTime = familySettings.overviewTime || '20:00';

            const [hours, minutes] = overviewTime.split(':');
            const now = new Date();
            if (now.getHours() !== parseInt(hours) || now.getMinutes() !== parseInt(minutes)) {
                continue;
            }

            try {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);
                tomorrow.setHours(0, 0, 0, 0);

                const tomorrowEnd = new Date(tomorrow);
                tomorrowEnd.setHours(23, 59, 59, 999);

                const weekEnd = new Date(tomorrow);
                weekEnd.setDate(weekEnd.getDate() + 7);
                weekEnd.setHours(23, 59, 59, 999);

                const [tomorrowEvents, weekEvents] = await Promise.all([
                    admin.firestore()
                        .collection('Events')
                        .where('familyId', '==', familyId)
                        .where('startDate', '>=', tomorrow)
                        .where('startDate', '<=', tomorrowEnd)
                        .orderBy('startDate')
                        .limit(3)
                        .get(),

                    admin.firestore()
                        .collection('Events')
                        .where('familyId', '==', familyId)
                        .where('startDate', '>', tomorrowEnd)
                        .where('startDate', '<=', weekEnd)
                        .orderBy('startDate')
                        .limit(3)
                        .get()
                ]);

                if (tomorrowEvents.empty && weekEvents.empty) {
                    continue;
                }

                let notificationBody = '';

                if (!tomorrowEvents.empty) {
                    notificationBody += 'Tomorrow: ';
                    const tomorrowTitles = tomorrowEvents.docs.map(doc => doc.data().title);
                    notificationBody += tomorrowTitles.join(', ');
                    if (tomorrowEvents.size === 3) notificationBody += ' and more...';
                }

                if (!weekEvents.empty) {
                    if (notificationBody) notificationBody += '\n\n';
                    notificationBody += 'This week: ';
                    const weekTitles = weekEvents.docs.map(doc => doc.data().title);
                    notificationBody += weekTitles.join(', ');
                    if (weekEvents.size === 3) notificationBody += ' and more...';
                }

                const pushTokens = await getPushTokensForFamily(familyId);
                await sendNotifications(pushTokens, {
                    title: "Family Calendar Overview",
                    body: notificationBody,
                    data: {
                        type: 'calendar_overview',
                        date: tomorrow.toISOString()
                    }
                });

                await storeNotification({
                    type: 'calendar_overview',
                    familyId,
                    content: notificationBody,
                    timestamp: admin.firestore.FieldValue.serverTimestamp(),
                });

            } catch (error) {
                console.error(`Error sending overview for family ${familyId}:`, error);
            }
        }
    });

exports.sendWeeklyOverview = functions.pubsub
    .schedule('0 20 * * 0')  // Runs at 8 PM every Sunday
    .onRun(async (context) => {
        const familiesSnapshot = await admin.firestore().collection('Families').get();

        for (const familyDoc of familiesSnapshot.docs) {
            const familyId = familyDoc.id;
            const familySettings = familyDoc.data()?.settings || {};
            const overviewTime = familySettings.weeklyOverviewTime || '20:00';

            const [hours, minutes] = overviewTime.split(':');
            const now = new Date();
            if (now.getHours() !== parseInt(hours) || now.getMinutes() !== parseInt(minutes)) {
                continue;
            }

            try {
                const weekStart = new Date();
                weekStart.setDate(weekStart.getDate() + 1);
                weekStart.setHours(0, 0, 0, 0);

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

                const weekEvents = await admin.firestore()
                    .collection('Events')
                    .where('familyId', '==', familyId)
                    .where('startDate', '>=', weekStart)
                    .where('startDate', '<=', weekEnd)
                    .orderBy('startDate')
                    .limit(3)
                    .get();

                if (weekEvents.empty) continue;

                const eventTitles = weekEvents.docs.map(doc => doc.data().title);
                const notificationBody = `Next week: ${eventTitles.join(', ')}${weekEvents.size === 3 ? ' and more...' : ''}`;

                const pushTokens = await getPushTokensForFamily(familyId);
                await sendNotifications(pushTokens, {
                    title: "Weekly Calendar Overview",
                    body: notificationBody,
                    data: {
                        type: 'weekly_overview',
                        weekStart: weekStart.toISOString()
                    }
                });

                await storeNotification({
                    type: 'weekly_overview',
                    familyId,
                    content: notificationBody,
                    timestamp: admin.firestore.FieldValue.serverTimestamp()
                });

            } catch (error) {
                console.error(`Error sending weekly overview for family ${familyId}:`, error);
            }
        }
    });

exports.checkUpcomingEvents = functions.pubsub
    .schedule("every 1 minutes")
    .onRun(async (context) => {
        const now = admin.firestore.Timestamp.now();
        const oneHourFromNow = new Date(now.toDate().getTime() + 60 * 60 * 1000);

        logger.info("Checking upcoming events", {
            currentTime: now.toDate().toISOString(),
            lookAheadTime: oneHourFromNow.toISOString()
        });

        const eventsSnapshot = await admin.firestore()
            .collection("Events")
            .where("startDate", ">=", now)
            .where("startDate", "<=", admin.firestore.Timestamp.fromDate(oneHourFromNow))
            .get();

        logger.info(`Found ${eventsSnapshot.size} upcoming events to check`);

        const processPromises = eventsSnapshot.docs.map(async (doc) => {
            const event = doc.data();
            const eventId = doc.id;

            // Skip if reminder already sent
            if (event.reminderSent === true ||
                event.notifiedAt ||
                (event.reminderSentAt &&
                    now.toMillis() - event.reminderSentAt.toMillis() < 60000)) {
                return;
            }
            try {
                const householdSnapshot = await admin.firestore()
                    .collection("Households")
                    .where("familyId", "==", event.familyId)
                    .limit(1)
                    .get();

                if (householdSnapshot.empty) {
                    return;
                }

                const householdDoc = householdSnapshot.docs[0];
                const householdSettings = householdDoc.data()?.settings || {};
                const reminderTime = householdSettings.defaultReminderTime || 15;
                const eventTime = event.startDate.toDate();
                const currentTime = now.toDate();
                const minutesUntilEvent = Math.round((eventTime - currentTime) / (60 * 1000));

                // Only send exactly at the reminder time
                if (minutesUntilEvent === reminderTime) {
                    // Double-check reminder hasn't been sent
                    const freshEventDoc = await doc.ref.get();
                    if (freshEventDoc.data()?.reminderSent === true) {
                        return;
                    }

                    // Mark as sent FIRST
                    await doc.ref.update({
                        reminderSent: true,
                        reminderSentAt: admin.firestore.FieldValue.serverTimestamp()
                    });

                    const pushTokens = await getPushTokensForFamily(event.familyId);
                    if (pushTokens.length > 0) {
                        // Send notification
                        await sendNotifications(pushTokens, {
                            title: "Upcoming Event Reminder",
                            body: `In ${reminderTime} minutes: ${event.title}`,
                            data: {
                                type: "event_reminder",
                                eventId: eventId,
                                familyId: event.familyId
                            }
                        });

                        // Store notification record
                        await storeNotification({
                            type: "event_reminder",
                            familyId: event.familyId,
                            content: `Reminder: ${event.title} starts in ${reminderTime} minutes`,
                            timestamp: admin.firestore.FieldValue.serverTimestamp(),
                            eventId: eventId
                        });

                        logger.info(`Sent reminder for event: ${event.title}`, {
                            eventId,
                            minutesUntilEvent,
                            reminderTime,
                            currentTime: currentTime.toISOString(),
                            eventTime: eventTime.toISOString()
                        });
                    }
                }
            } catch (error) {
                logger.error(`Error processing reminder for event ${eventId}:`, error);
            }
        });

        await Promise.all(processPromises);
    });
// 3. Add a function to reset reminder flags for testing
exports.resetReminderFlags = functions.https.onRequest(async (req, res) => {
    try {
        const batch = admin.firestore().batch();
        const events = await admin.firestore()
            .collection("Events")
            .where("reminderSent", "==", true)
            .get();

        events.docs.forEach(doc => {
            batch.update(doc.ref, {
                reminderSent: false,
                reminderSentAt: null
            });
        });

        await batch.commit();
        res.status(200).send(`Reset ${events.size} events`);
    } catch (error) {
        res.status(500).send(error.message);
    }
});

exports.initializeEventFlags = functions.firestore
    .document('Events/{eventId}')
    .onCreate(async (snapshot, context) => {
        try {
            const eventData = snapshot.data();
            if (eventData.reminderSent === undefined) {
                await snapshot.ref.update({
                    reminderSent: false,
                    reminderSentAt: null
                });
            }
        } catch (error) {
            logger.error(`Error initializing event ${context.params.eventId}:`, error);
        }
    });

/* ─────────────────────────────────
   MICROSOFT SUBSCRIPTION & WEBHOOK FUNCTIONS
───────────────────────────────── */
// Renew Microsoft Subscriptions every 24 hours
exports.renewMicrosoftSubscriptions = functions.pubsub
    .schedule("every 24 hours")
    .onRun(async (context) => {
        const now = Date.now();
        const subs = await db.collection("MicrosoftSubscriptions")
            .where("expirationDateTime", "<=", new Date(now + 24 * 60 * 60 * 1000))
            .get();
        if (subs.empty) return null;
        const profilesSnapshot = await db.collection("Profiles")
            .where("uid", "in", subs.docs.map(doc => doc.id))
            .get();
        const batch = db.batch();
        const userTokenMap = {};
        profilesSnapshot.forEach(doc => {
            const data = doc.data();
            if (data.microsoftAccounts) {
                const [email, token] = Object.entries(data.microsoftAccounts)[0] || [];
                if (token) userTokenMap[doc.id] = { token, email };
            }
        });
        for (const subDoc of subs.docs) {
            const userId = subDoc.id;
            const userTokens = userTokenMap[userId];
            if (!userTokens) continue;
            try {
                const subscription = {
                    changeType: "created,updated,deleted",
                    notificationUrl: `https://us-central1-cally-family-calendar.cloudfunctions.net/microsoftCalendarWebhook?userId=${userId}`,
                    resource: "/me/calendar/events",
                    expirationDateTime: new Date(now + 3 * 24 * 60 * 60 * 1000).toISOString(),
                    clientState: userId,
                };
                const response = await fetch("https://graph.microsoft.com/v1.0/subscriptions", {
                    method: "POST",
                    headers: {
                        Authorization: `Bearer ${userTokens.token}`,
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify(subscription),
                });
                if (!response.ok) throw new Error(await response.text());
                const subscriptionData = await response.json();
                batch.set(db.collection("MicrosoftSubscriptions").doc(userId), {
                    subscriptionId: subscriptionData.id,
                    expirationDateTime: subscriptionData.expirationDateTime,
                    updatedAt: FieldValue.serverTimestamp(),
                }, { merge: true });
            } catch (error) {
                logger.error(`Failed to renew Microsoft subscription for ${userId}`, error);
                batch.set(db.collection("MicrosoftSubscriptionErrors").doc(), {
                    userId,
                    error: error.message,
                    timestamp: FieldValue.serverTimestamp(),
                });
            }
        }
        await batch.commit();
    });

// Microsoft Calendar Webhook
exports.microsoftCalendarWebhook = onRequest(async (req, res) => {
    const userId = req.query.userId;
    try {
        const userDoc = await db.collection("Profiles").doc(userId).get();
        const userData = userDoc.data();
        if (!userData?.microsoftAccounts) return res.status(200).send();
        const [email, token] = Object.entries(userData.microsoftAccounts)[0] || [];
        if (!token) return res.status(200).send();
        await fetchAndSaveMicrosoftEvents({ token, email, familyId: userData.familyId, creatorId: userId });
        return res.status(200).send();
    } catch (error) {
        logger.error(`Error processing Microsoft webhook for ${userId}`, error);
        return res.status(500).send();
    }
});

/* ─────────────────────────────────
   CLEANUP TOKEN REFRESH FLAGS
───────────────────────────────── */
const tokenRefreshInProgress = new Map();
exports.cleanupTokenRefreshFlags = functions.pubsub
    .schedule("every 5 minutes")
    .onRun(() => {
        tokenRefreshInProgress.clear();
        logger.info("[CLEANUP] Cleared all token refresh flags");
        return null;
    });

/* ─────────────────────────────────
   FIRESTORE EVENT SYNC FUNCTIONS (Google)
───────────────────────────────── */
// Create new event sync (skip if externalOrigin is google)
exports.syncNewEventToGoogle = functions.firestore
    .document("Events/{eventId}")
    .onCreate(async (snapshot, context) => {
        const newEvent = snapshot.data();
        const eventId = context.params.eventId;

        if (newEvent.reminderSent === undefined) {
            await snapshot.ref.update({
                reminderSent: false,
                eveningReminderSent: false,
                notifiedAt: null
            });
        }

        if (newEvent.externalOrigin === "google") {
            logger.info("[GOOGLE_SYNC] Skipping sync for Google-originated event", eventId);
            return;
        }
        try {
            const creatorDoc = await db.collection("Profiles").doc(newEvent.creatorId).get();
            const creatorData = creatorDoc.data();
            if (!creatorData?.googleAccounts) {
                logger.info("[GOOGLE_SYNC] Creator has no Google accounts", newEvent.creatorId);
                return;
            }
            const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
            if (!accountData?.accessToken) {
                logger.info("[GOOGLE_SYNC] No access token found for creator", newEvent.creatorId);
                return;
            }
            await syncEventToGoogle(
                {
                    ...newEvent,
                    email,
                    startDate: new Date(newEvent.startDate.seconds * 1000),
                    endDate: new Date(newEvent.endDate.seconds * 1000),
                },
                accountData.accessToken,
                accountData.refreshToken,
                newEvent.creatorId
            );
            logger.info("[GOOGLE_SYNC] Successfully synced new event to Google", eventId);
        } catch (error) {
            logger.error("[GOOGLE_SYNC] Error syncing new event to Google", error);
        }
    });

// Update event sync to Google
exports.syncEventToGoogleOnUpdate = functions.firestore
    .document("Events/{eventId}")
    .onUpdate(async (change, context) => {
        const eventBefore = change.before.data();
        const eventAfter = change.after.data();
        const eventId = context.params.eventId;
        if (eventAfter.externalOrigin === "google") {
            logger.info("[GOOGLE_SYNC] Skipping sync for Google-originated event", eventId);
            return;
        }
        if (JSON.stringify(eventBefore) === JSON.stringify(eventAfter)) {
            logger.info("[GOOGLE_SYNC] No changes detected for event", eventId);
            return;
        }
        try {
            const creatorDoc = await db.collection("Profiles").doc(eventAfter.creatorId).get();
            const creatorData = creatorDoc.data();
            if (!creatorData?.googleAccounts) {
                logger.info("[GOOGLE_SYNC] Creator has no Google accounts", eventAfter.creatorId);
                return;
            }
            const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
            if (!accountData?.accessToken) {
                logger.info("[GOOGLE_SYNC] No access token found for creator", eventAfter.creatorId);
                return;
            }
            const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
            const googleEvent = {
                summary: eventAfter.title,
                start: {
                    dateTime: eventAfter.allDay ? undefined : new Date(eventAfter.startDate.seconds * 1000).toISOString(),
                    date: eventAfter.allDay ? new Date(eventAfter.startDate.seconds * 1000).toISOString().split("T")[0] : undefined,
                    timeZone: "UTC",
                },
                end: {
                    dateTime: eventAfter.allDay ? undefined : new Date(eventAfter.endDate.seconds * 1000).toISOString(),
                    date: eventAfter.allDay ? new Date(new Date(eventAfter.endDate.seconds * 1000).getTime() + 24 * 60 * 60 * 1000).toISOString().split("T")[0] : undefined,
                    timeZone: "UTC",
                },
                visibility: eventAfter.private ? "private" : "default",
                status: "confirmed",
                reminders: { useDefault: true },
            };
            let response = await fetch(url, {
                method: "PATCH",
                headers: {
                    Authorization: `Bearer ${accountData.accessToken}`,
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(googleEvent),
            });
            if (response.status === 401 && accountData.refreshToken) {
                logger.info("[GOOGLE_SYNC] Token expired, refreshing...");
                const { refreshedGoogleToken } = await refreshGoogleToken(accountData.refreshToken);
                await db.collection("Profiles").doc(eventAfter.creatorId).update({
                    [`googleAccounts.${email}.accessToken`]: refreshedGoogleToken,
                });
                response = await fetch(url, {
                    method: "PATCH",
                    headers: {
                        Authorization: `Bearer ${refreshedGoogleToken}`,
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify(googleEvent),
                });
            }
            if (response.status === 404) {
                logger.info("[GOOGLE_SYNC] Event not found in Google Calendar, creating new event");
                const insertUrl = "https://www.googleapis.com/calendar/v3/calendars/primary/events";
                response = await fetch(insertUrl, {
                    method: "POST",
                    headers: {
                        Authorization: `Bearer ${accountData.accessToken}`,
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({ ...googleEvent, id: eventId }),
                });
            }
            if (!response.ok) {
                const errorData = await response.json();
                throw new Error(errorData.error?.message || response.statusText);
            }
            logger.info("[GOOGLE_SYNC] Successfully synced event update to Google", eventId);
        } catch (error) {
            logger.error("[GOOGLE_SYNC] Error syncing event update to Google", error);
        }
    });

// Delete event sync from Google
exports.syncEventToGoogleOnDelete = functions.firestore
    .document("Events/{eventId}")
    .onDelete(async (snapshot, context) => {
        const deletedEvent = snapshot.data();
        const eventId = context.params.eventId;
        if (deletedEvent.externalOrigin === "google") {
            logger.info("[GOOGLE_SYNC] Skipping delete sync for Google-originated event", eventId);
            return;
        }
        try {
            const creatorDoc = await db.collection("Profiles").doc(deletedEvent.creatorId).get();
            const creatorData = creatorDoc.data();
            if (!creatorData?.googleAccounts) {
                logger.info("[GOOGLE_SYNC] Creator has no Google accounts", deletedEvent.creatorId);
                return;
            }
            const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
            if (!accountData?.accessToken) {
                logger.info("[GOOGLE_SYNC] No access token found for creator", deletedEvent.creatorId);
                return;
            }
            const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
            let response = await fetch(url, {
                method: "DELETE",
                headers: {
                    Authorization: `Bearer ${accountData.accessToken}`,
                },
            });
            if (response.status === 401 && accountData.refreshToken) {
                logger.info("[GOOGLE_SYNC] Token expired, refreshing...");
                const { refreshedGoogleToken } = await refreshGoogleToken(accountData.refreshToken);
                await db.collection("Profiles").doc(deletedEvent.creatorId).update({
                    [`googleAccounts.${email}.accessToken`]: refreshedGoogleToken,
                });
                response = await fetch(url, {
                    method: "DELETE",
                    headers: { Authorization: `Bearer ${refreshedGoogleToken}` },
                });
            }
            if (!response.ok && response.status !== 404) {
                const errorData = await response.json();
                throw new Error(errorData.error?.message || response.statusText);
            }
            logger.info("[GOOGLE_SYNC] Successfully deleted event from Google", eventId);
        } catch (error) {
            logger.error("[GOOGLE_SYNC] Error deleting event from Google", error);
        }
    });

/* ─────────────────────────────────
   UTILS FOR FETCH EVENTS (CALLABLE)
───────────────────────────────── */
const createEventHash = (event) => {
    const str = `${event.startDate?.seconds || ""}-${event.endDate?.seconds || ""}-${
        event.title || ""
    }-${event.location || ""}-${event.allDay ? "true" : "false"}`;
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
        hash = ((hash << 5) - hash) + str.charCodeAt(i);
        hash = hash & hash;
    }
    return hash.toString(36);
};

async function fetchEventsFromFirestore(userId, profileData, isFamilyView) {
    const eventsQuery = db.collection("Events");
    let constraints;
    const familyId = profileData?.familyId;
    if (profileData?.userType === "FAMILY_DEVICE") {
        constraints = [ eventsQuery.where("familyId", "==", familyId) ];
    } else {
        if (isFamilyView) {
            constraints = [
                eventsQuery.where("familyId", "==", familyId),
                eventsQuery.where("creatorId", "==", userId),
                eventsQuery.where("attendees", "array-contains", userId),
            ];
        } else {
            constraints = [
                eventsQuery.where("creatorId", "==", userId),
                eventsQuery.where("attendees", "array-contains", userId),
            ];
        }
    }
    try {
        const snapshots = await Promise.all(constraints.map(query => query.get()));
        const uniqueEvents = new Map();
        const processedHashes = new Set();
        const creatorIds = new Set();
        snapshots.forEach(snapshot => {
            snapshot.docs.forEach(doc => {
                const event = doc.data();
                const hash = createEventHash(event);
                if (!processedHashes.has(hash)) {
                    processedHashes.add(hash);
                    creatorIds.add(event.creatorId);
                    uniqueEvents.set(doc.id, event);
                }
            });
        });
        const creatorIdsArray = Array.from(creatorIds);
        const creatorProfiles = new Map();
        const BATCH_SIZE = 10;
        for (let i = 0; i < creatorIdsArray.length; i += BATCH_SIZE) {
            const chunk = creatorIdsArray.slice(i, i + BATCH_SIZE);
            const profilesSnapshot = await db.collection("Profiles")
                .where(FieldPath.documentId(), "in", chunk)
                .get();
            profilesSnapshot.docs.forEach(doc => {
                creatorProfiles.set(doc.id, doc.data()?.eventColor || "#ff69b4");
            });
        }
        return Array.from(uniqueEvents.entries()).map(([id, event]) => ({
            ...event,
            id,
            start: event.allDay ? new Date(new Date(event.startDate.seconds * 1000).setHours(0, 0, 0, 0))
                : new Date(event.startDate.seconds * 1000),
            end: event.allDay ? new Date(new Date(event.endDate.seconds * 1000).setHours(0, 0, 0, 0))
                : new Date(event.endDate.seconds * 1000),
            hideHours: event.allDay,
            eventColor: creatorProfiles.get(event.creatorId) || "#ff69b4",
            notes: event.notes,
        }));
    } catch (error) {
        logger.error("Error fetching events", error);
        throw new functions.https.HttpsError("internal", "Error fetching events");
    }
}

exports.fetchEvents = functions.https.onCall(async (data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError("unauthenticated", "User must be authenticated");
    }
    try {
        const { isFamilyView } = data;
        const userId = context.auth.uid;
        const profileDoc = await db.collection("Profiles").doc(userId).get();
        if (!profileDoc.exists) {
            throw new functions.https.HttpsError("not-found", "User profile not found");
        }
        const profileData = profileDoc.data();
        const events = await fetchEventsFromFirestore(userId, profileData, isFamilyView);
        return { events };
    } catch (error) {
        logger.error("Error in fetchEvents", error);
        throw new functions.https.HttpsError("internal", error.message || "An unknown error occurred");
    }
});

/* ─────────────────────────────────
   HTTP FUNCTIONS (e.g., createSubUser, removeSubUser, generateCustomToken)
───────────────────────────────── */
// Create Sub User
exports.createSubUser = onRequest(async (req, res) => {
    const authHeader = req.get("Authorization");
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
        logger.warn("Missing or incorrect Authorization header", { authHeader });
        return res.status(401).json({ error: "Unauthorized" });
    }
    try {
        const token = authHeader.split("Bearer ")[1];
        logger.info("Verifying ID token", { token });
        let decodedToken;
        try {
            decodedToken = await getAuth().verifyIdToken(token);
            logger.info("ID token verified successfully", { uid: decodedToken.uid });
        } catch (verifyError) {
            logger.error("ID token verification failed", { error: verifyError.message });
            return res.status(401).json({ error: "Unauthorized: Invalid token" });
        }
        const { userType, firstName, lastName, email, password, familyId } = req.body.data;
        if (!email || !password || !firstName || !userType || !familyId) {
            logger.warn("Missing required fields in request body", { requestBody: req.body.data });
            return res.status(400).json({ error: "Missing required fields" });
        }
        let userRecord;
        try {
            userRecord = await getAuth().createUser({
                email,
                password,
                displayName: `${firstName} ${lastName}`,
            });
            logger.info("User record created", { userId: userRecord.uid });
        } catch (createUserError) {
            logger.error("User creation failed", { error: createUserError.message });
            return res.status(500).json({ error: "Failed to create user" });
        }
        const userProfile = { userType, firstName, lastName, familyId, email, uid: userRecord.uid };
        try {
            await getFirestore().collection("Profiles").doc(userRecord.uid).set(userProfile);
            logger.info("User profile saved to Firestore", { userId: userRecord.uid });
        } catch (firestoreError) {
            logger.error("Failed to save user profile to Firestore", { error: firestoreError.message });
            return res.status(500).json({ error: "Failed to save user profile" });
        }
        return res.status(200).json({ data: { message: "User created successfully", userId: userRecord.uid } });
    } catch (error) {
        logger.error("Error in createSubUser function", { error: error.message });
        return res.status(500).json({ data: { error: error.message } });
    }
});

// Remove Sub User
exports.removeSubUser = onRequest(async (req, res) => {
    const authHeader = req.get("Authorization");
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
        logger.warn("Missing or incorrect Authorization header", { authHeader });
        return res.status(401).json({ error: "Unauthorized" });
    }
    try {
        const token = authHeader.split("Bearer ")[1];
        logger.info("Verifying ID token", { token });
        let decodedToken;
        try {
            decodedToken = await getAuth().verifyIdToken(token);
            logger.info("ID token verified successfully", { uid: decodedToken.uid });
        } catch (verifyError) {
            logger.error("ID token verification failed", { error: verifyError.message });
            return res.status(401).json({ error: "Unauthorized: Invalid token" });
        }
        const { userId, familyId } = req.body.data;
        if (!userId || !familyId) {
            logger.warn("Missing required fields in request body", { requestBody: req.body.data });
            return res.status(400).json({ error: "Missing required fields" });
        }
        const userProfileDoc = await getFirestore().collection("Profiles").doc(userId).get();
        if (!userProfileDoc.exists) {
            logger.error("User profile not found", { userId });
            return res.status(404).json({ error: "User not found" });
        }
        if (userProfileDoc.data().familyId !== familyId) {
            logger.error("User does not belong to the specified family", {
                userId,
                requestedFamilyId: familyId,
                actualFamilyId: userProfileDoc.data().familyId,
            });
            return res.status(403).json({ error: "User does not belong to the specified family" });
        }
        await getFirestore().collection("Profiles").doc(userId).delete();
        logger.info("User profile deleted from Firestore", { userId });
        await getAuth().deleteUser(userId);
        logger.info("User authentication deleted", { userId });
        return res.status(200).json({ data: { message: "User removed successfully", success: true } });
    } catch (error) {
        logger.error("Error in removeSubUser function", { error: error.message });
        return res.status(500).json({ data: { error: error.message } });
    }
});

// Generate Custom Token
exports.generateCustomToken = onRequest(async (req, res) => {
    try {
        const { userId } = req.body.data;
        logger.info("Generating custom token for userId", { userId });
        if (!userId) return res.status(400).json({ error: "Missing userId" });
        const customToken = await getAuth().createCustomToken(userId);
        return res.status(200).json({ data: { token: customToken } });
    } catch (error) {
        logger.error("Error generating custom token", { error: error.message });
        return res.status(500).json({ error: "Failed to generate custom token" });
    }
});

async function syncEventToGoogle(event, accessToken, refreshToken, creatorId) {
    try {
        logger.info('[GOOGLE_SYNC] Starting to sync event to Google Calendar', {
            eventId: event.id,
            creatorId
        });

        let token = accessToken;
        const googleEvent = {
            summary: event.title,
            start: {
                dateTime: event.allDay ? undefined : event.startDate.toISOString(),
                date: event.allDay ? event.startDate.toISOString().split('T')[0] : undefined,
                timeZone: 'UTC'
            },
            end: {
                dateTime: event.allDay ? undefined : event.endDate.toISOString(),
                date: event.allDay ? new Date(event.endDate.getTime() + 24*60*60*1000).toISOString().split('T')[0] : undefined,
                timeZone: 'UTC'
            },
            visibility: event.private ? 'private' : 'default',
            status: 'confirmed',
            reminders: {
                useDefault: true
            },
            // Add extendedProperties to store our Firestore ID
            extendedProperties: {
                private: {
                    firestoreId: event.id
                }
            }
        };

        // For new events, use POST to create
        const url = 'https://www.googleapis.com/calendar/v3/calendars/primary/events';
        let response = await fetch(url, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(googleEvent)
        });

        // Handle token refresh if needed
        if (response.status === 401 && refreshToken) {
            logger.info('[GOOGLE_SYNC] Token expired, refreshing...');
            const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
            token = refreshedGoogleToken;

            // Update the token in Firestore
            await db.collection("Profiles").doc(creatorId).update({
                [`googleAccounts.${event.email}.accessToken`]: refreshedGoogleToken
            });

            // Retry with new token
            response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${refreshedGoogleToken}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(googleEvent)
            });
        }

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(`Failed to sync event: ${errorData.error?.message || response.statusText}`);
        }

        const responseData = await response.json();

        // Store the Google Calendar event ID in Firestore
        await db.collection('Events').doc(event.id).update({
            googleEventId: responseData.id
        });

        logger.info('[GOOGLE_SYNC] Successfully synced event to Google Calendar', {
            firestoreId: event.id,
            googleEventId: responseData.id
        });

        return true;
    } catch (error) {
        logger.error('[GOOGLE_SYNC] Error syncing event to Google Calendar:', error);
        throw error;
    }
}



/* ─────────────────────────────────
   PUSH NOTIFICATION HELPERS
───────────────────────────────── */
async function getPushTokensForFamily(familyId, excludeUserId = null) {
    const snapshot = await db.collection("Profiles")
        .where("familyId", "==", familyId)
        .get();

    let pushTokens = [];
    logger.info("Getting push tokens", {familyId, excludeUserId});
    snapshot.forEach(doc => {
        const data = doc.data();
        const userId = doc.id;
        logger.debug("Processing user", {
            docId: userId,
            hasToken: !!data.pushToken,
            excluded: userId === excludeUserId
        });
        if (userId !== excludeUserId && data.pushToken) {
            logger.info("Including token for user", {userId, excludeUserId});
            pushTokens.push(data.pushToken);
        } else {
            logger.debug("Excluding token for user", {userId, excludeUserId});
        }
    });
    // Remove duplicates before sending
    return [...new Set(pushTokens)];
}

async function removeInvalidPushToken(pushToken) {
    try {
        const snapshot = await db.collection("Profiles")
            .where("pushToken", "==", pushToken)
            .get();
        const batch = db.batch();
        snapshot.forEach(doc => batch.update(doc.ref, {pushToken: FieldValue.delete()}));
        await batch.commit();
        logger.info(`Removed invalid push token: ${pushToken}`);
    } catch (error) {
        logger.error("Error removing invalid push token", error);
    }
}

async function sendNotifications(pushTokens, notification) {
    if (!pushTokens.length) return;
    const messages = pushTokens
        .filter(token => Expo.isExpoPushToken(token))
        .map(token => ({
            to: token,
            sound: "default",
            priority: "high",
            ...notification,
        }));
    const chunks = expo.chunkPushNotifications(messages);
    for (let chunk of chunks) {
        try {
            const tickets = await expo.sendPushNotificationsAsync(chunk);
            tickets.forEach(ticket => {
                if (ticket.status === "error") {
                    if (ticket.details?.error === "DeviceNotRegistered") {
                        removeInvalidPushToken(ticket.to);
                    }
                    logger.error("Push notification error", ticket.message);
                }
            });
        } catch (error) {
            logger.error("Error sending notifications", error);
        }
    }
}

async function storeNotification(notificationData) {
    try {
        await db.collection("Notifications").add(notificationData);
    } catch (error) {
        logger.error("Error storing notification", error);
    }
}

/* ─────────────────────────────────
   TOKEN REFRESH HELPERS (unchanged)
───────────────────────────────── */
// [refreshGoogleToken and refreshMicrosoftToken remain unchanged as in your code]

/* ─────────────────────────────────
   NEW NOTIFICATION TRIGGER – No Batching
───────────────────────────────── */

/*
We now remove the “batch processing” functions
and instead trigger notifications directly when an event is created.
We also use a field named "notifiedAt" to ensure that repeated triggers (or re‑processing on update) don’t result in duplicate notifications.
*/

// Firestore trigger: send notification immediately when a new event is created (if not already notified).
exports.notifyOnEventCreation = functions.firestore
    .document("Events/{eventId}")
    .onCreate(async (snapshot, context) => {
        const event = snapshot.data();
        const eventId = context.params.eventId;

        // If the event was created by an automated sync (for example, externalOrigin === "google"), skip notification.
        if (event.externalOrigin === "google") {
            logger.info("[NOTIFY] Skipping notification for Google-originated event", eventId);
            return;
        }

        // Check if already notified (could be set by an update trigger)
        if (event.notifiedAt) {
            logger.info("[NOTIFY] Event already notified", eventId);
            return;
        }

        // Construct a notification message (customize as needed)
        const notificationMessage = `New event "${event.title}" added to the calendar.`;

        try {
            // Get push tokens excluding the creator
            const pushTokens = await getPushTokensForFamily(event.familyId, event.creatorId);

            if (pushTokens.length) {
                await sendNotifications(pushTokens, {
                    title: "New Family Calendar Event",
                    body: notificationMessage,
                    data: {type: "event_created", eventId},
                });
                await storeNotification({
                    type: "event_created",
                    familyId: event.familyId,
                    content: notificationMessage,
                    timestamp: FieldValue.serverTimestamp(),
                    eventId,
                });
            }
            // Mark event as notified (to prevent duplicate notifications on re-trigger)
            await snapshot.ref.update({notifiedAt: FieldValue.serverTimestamp()});
            logger.info("[NOTIFY] Notification sent for event", eventId);
        } catch (error) {
            logger.error("[NOTIFY] Error sending notification for event", eventId, error);
        }
    });

/*
You can create similar triggers for event updates if needed.
For example, if the event update includes changes that warrant a new notification (and if you clear or update the notifiedAt field accordingly).
This simplified approach reduces extra writes because you update the event document only once to mark it as notified.
*/

// A sample Firestore trigger for critical updates (if necessary):
exports.notifyOnEventUpdate = functions.firestore
    .document("Events/{eventId}")
    .onUpdate(async (change, context) => {
        const before = change.before.data();
        const after = change.after.data();
        const eventId = context.params.eventId;

        // Only trigger if certain fields have changed (e.g., title change or a significant update)
        if (before.title === after.title) {
            logger.info("[NOTIFY] No relevant change detected; skipping update notification", eventId);
            return;
        }

        // Use a different notification type for updates
        const notificationMessage = `Event "${after.title}" has been updated.`;

        try {
            const pushTokens = await getPushTokensForFamily(after.familyId, after.creatorId);
            if (pushTokens.length) {
                await sendNotifications(pushTokens, {
                    title: "Family Calendar Event Updated",
                    body: notificationMessage,
                    data: {type: "event_updated", eventId},
                });
                await storeNotification({
                    type: "event_updated",
                    familyId: after.familyId,
                    content: notificationMessage,
                    timestamp: FieldValue.serverTimestamp(),
                    eventId,
                });
            }
            // Optionally update a notification timestamp on the event document.
            await change.after.ref.update({notifiedAt: FieldValue.serverTimestamp()});
            logger.info("[NOTIFY] Update notification sent for event", eventId);
        } catch (error) {
            logger.error("[NOTIFY] Error sending update notification for event", eventId, error);
        }
    });

/* ─────────────────────────────────
   MIGRATION UTILITY
───────────────────────────────── */
exports.migrateEventNotifications = functions.https.onRequest(async (req, res) => {
    try {
        const batch = db.batch();
        const existingEvents = await db.collection('Events')
            .where('notifiedAt', '==', null)
            .get();

        logger.info("[MIGRATE] Starting event migration", {count: existingEvents.size});

        if (existingEvents.empty) {
            logger.info("[MIGRATE] No events require migration");
            res.status(200).send("No events needed migration");
            return;
        }

        existingEvents.forEach(doc => {
            batch.update(doc.ref, {
                notifiedAt: FieldValue.serverTimestamp(),
                eveningReminderSent: false,
                reminderSent: false,
                lastNotifiedAt: null
            });
        });

        await batch.commit();

        // Store migration record
        await db.collection('SystemLogs').add({
            type: 'migration',
            operation: 'event_notifications',
            count: existingEvents.size,
            timestamp: FieldValue.serverTimestamp(),
            status: 'completed'
        });

        logger.info("[MIGRATE] Completed event migration", {
            count: existingEvents.size,
            status: 'success'
        });

        res.status(200).send({
            status: 'success',
            message: `Successfully migrated ${existingEvents.size} events`,
            timestamp: new Date().toISOString()
        });

    } catch (error) {
        logger.error("[MIGRATE] Error during migration", {
            error: error.message,
            stack: error.stack
        });

        // Store error record
        await db.collection('SystemLogs').add({
            type: 'migration',
            operation: 'event_notifications',
            error: error.message,
            errorStack: error.stack,
            timestamp: FieldValue.serverTimestamp(),
            status: 'failed'
        });

        res.status(500).send({
            status: 'error',
            message: 'Migration failed',
            error: error.message,
            timestamp: new Date().toISOString()
        });
    }
});

exports.sendSyncNotification = onRequest(async (req, res) => {
    const userId = req.query.userId;

    if (!userId) {
        logger.error('[SYNC] Missing userId in request');
        return res.status(400).send('Missing userId');
    }

    try {
        const userDoc = await db.collection("Profiles").doc(userId).get();
        if (!userDoc.exists) {
            logger.error(`[SYNC] No profile found for user ${userId}`);
            return res.status(404).send("User profile not found");
        }

        const userData = userDoc.data();
        const familyId = userData.familyId;
        const googleAccounts = userData.googleAccounts || {};

        // Get first Google account
        const [email] = Object.keys(googleAccounts);
        if (!email) {
            logger.error(`[SYNC] No Google account found for user ${userId}`);
            return res.status(400).send("No Google account found");
        }

        const accountData = googleAccounts[email];
        const { accessToken, refreshToken } = accountData;

        if (!accessToken) {
            logger.error(`[SYNC] No access token for user ${userId}`);
            return res.status(400).send("No access token found");
        }

        if (!familyId) {
            logger.error(`[SYNC] No family ID for user ${userId}`);
            return res.status(400).send("No family ID found");
        }

        // Check if household exists and create if it doesn't
        const householdsSnapshot = await db.collection('Households')
            .where("familyId", "==", familyId)
            .get();

        if (householdsSnapshot.empty) {
            logger.info(`[SYNC] No household found for family ${familyId}, creating one`);
            // Create a new household document
            await db.collection('Households').add({
                familyId,
                createdAt: admin.firestore.FieldValue.serverTimestamp(),
                createdBy: userId,
                lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp(),
                settings: {
                    defaultReminderTime: 30, // Default 30 minutes reminder
                }
            });
            logger.info(`[SYNC] Created new household for family ${familyId}`);
        }

        logger.info(`[SYNC] Starting calendar sync for user ${userId}`);
        const syncStartTime = Date.now();

        // Trigger immediate sync and get event count
        const totalEvents = await fetchAndSaveGoogleEvents({
            token: accessToken,
            refreshToken,
            email,
            familyId,
            creatorId: userId
        });

        // Update household timestamps (will now include newly created household if any)
        const updatedHouseholdsSnapshot = await db.collection('Households')
            .where("familyId", "==", familyId)
            .get();

        const batch = db.batch();
        updatedHouseholdsSnapshot.docs.forEach((doc) => {
            batch.update(doc.ref, {
                lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp()
            });
        });

        await batch.commit();

        logger.info(`[SYNC] Completed sync for user ${userId} in ${Date.now() - syncStartTime}ms`);
        res.status(200).send("Sync completed successfully");
    } catch (error) {
        logger.error(`[SYNC] Error in sync notification:`, error);
        res.status(500).send("Sync failed");
    }
});

exports.renewGoogleCalendarWatch = functions.pubsub
    .schedule("every 10 minutes")
    .onRun(async (context) => {
        logger.info("[WATCH] Starting calendar watch renewal check");

        try {
            const profilesWithGoogle = await db.collection('Profiles')
                .where('googleAccounts', '!=', null)
                .get();

            logger.info(`[WATCH] Found ${profilesWithGoogle.size} profiles with Google accounts`);

            const existingWatches = await db.collection('CalendarWatches').get();
            logger.info(`[WATCH] Found ${existingWatches.size} existing watches`);

            const now = Date.now();
            const watchMap = new Map();
            existingWatches.forEach(doc => {
                watchMap.set(doc.id, doc.data());
                logger.debug(`[WATCH] Existing watch for ${doc.id}`, doc.data());
            });

            const batch = db.batch();
            let processedCount = 0;
            let newWatchCount = 0;
            let renewalCount = 0;

            for (const profile of profilesWithGoogle.docs) {
                const userId = profile.id;
                const userData = profile.data();
                const existingWatch = watchMap.get(userId);

                logger.info(`[WATCH] Processing user ${userId}`, {
                    hasGoogleAccounts: !!userData.googleAccounts,
                    accountCount: Object.keys(userData.googleAccounts || {}).length,
                    hasExistingWatch: !!existingWatch,
                    watchExpiration: existingWatch?.expiration
                });

                if (!userData.googleAccounts || Object.keys(userData.googleAccounts).length === 0) {
                    logger.info(`[WATCH] Skipping user ${userId} - no Google accounts`);
                    continue;
                }

                const firstAccount = Object.values(userData.googleAccounts)[0];
                const token = firstAccount?.accessToken;

                if (!token) {
                    logger.info(`[WATCH] No token found for user ${userId}`);
                    continue;
                }

                const needsRenewal = !existingWatch ||
                    !existingWatch.expiration ||
                    existingWatch.expiration < (now + 30 * 60 * 1000);

                if (!needsRenewal) {
                    logger.info(`[WATCH] Watch still valid for user ${userId}, skipping renewal`);
                    continue;
                }

                try {
                    logger.info(`[WATCH] ${existingWatch ? 'Renewing' : 'Creating new'} watch for user ${userId}`);

                    const url = `https://www.googleapis.com/calendar/v3/calendars/${GOOGLE_CALENDAR_ID}/events/watch`;
                    const watchBody = {
                        id: `${CHANNEL_ID}-${userId}`,
                        type: "web_hook",
                        address: `${WEBHOOK_URL}?userId=${userId}`,
                        params: {ttl: "604800"}
                    };

                    logger.info(`[WATCH] Making request for user ${userId}`, {
                        url,
                        watchBody,
                        tokenPrefix: token.substring(0, 10)
                    });

                    const response = await fetch(url, {
                        method: "POST",
                        headers: {
                            Authorization: `Bearer ${token}`,
                            "Content-Type": "application/json",
                        },
                        body: JSON.stringify(watchBody),
                    });

                    const responseText = await response.text();
                    logger.info(`[WATCH] Received response for ${userId}`, {
                        status: response.status,
                        response: responseText
                    });

                    if (!response.ok) {
                        throw new Error(`Failed to set up watch: ${responseText}`);
                    }

                    const result = JSON.parse(responseText);

                    batch.set(db.collection('CalendarWatches').doc(userId), {
                        watchId: result.id,
                        resourceId: result.resourceId,
                        expiration: result.expiration,
                        updatedAt: admin.firestore.FieldValue.serverTimestamp(),
                    }, {merge: true});

                    processedCount++;
                    if (existingWatch) {
                        renewalCount++;
                    } else {
                        newWatchCount++;
                    }

                    logger.info(`[WATCH] Successfully ${existingWatch ? 'renewed' : 'created'} watch for user ${userId}`, {
                        watchId: result.id,
                        expiration: result.expiration
                    });

                } catch (error) {
                    logger.error(`[WATCH] Failed to ${existingWatch ? 'renew' : 'create'} watch for user ${userId}:`, {
                        error: error.message,
                        stack: error.stack
                    });

                    batch.set(db.collection('CalendarWatchErrors').doc(), {
                        userId,
                        error: error.message,
                        timestamp: admin.firestore.FieldValue.serverTimestamp(),
                    });
                }
            }

            if (processedCount > 0) {
                logger.info(`[WATCH] Committing batch with ${processedCount} operations`);
                await batch.commit();
            }

            logger.info(`[WATCH] Completed calendar watch processing`, {
                totalProcessed: processedCount,
                newWatches: newWatchCount,
                renewals: renewalCount
            });

            return null;
        } catch (error) {
            logger.error('[WATCH] Critical error in watch renewal:', {
                error: error.message,
                stack: error.stack
            });
            throw error;
        }
    });

exports.triggerGoogleSync = onCall({
    memory: '256MiB',
    enforceAppCheck: false,
    maxInstances: 10,
    region: 'us-central1',
    invoker: 'public',
}, async (request) => {
    if (!request.auth) {
        throw new HttpsError('unauthenticated', 'Authentication required');
    }

    try {
        const userId = request.auth.uid;
        const {email} = request.data;

        logger.info("Starting manual Google sync", {userId, email});

        const userDoc = await db.collection("Profiles").doc(userId).get();
        const userData = userDoc.data();

        if (!userData?.googleAccounts?.[email]) {
            logger.error("No valid Google account found", {userId, email});
            throw new HttpsError('failed-precondition', 'No valid Google account found');
        }

        const accountData = userData.googleAccounts[email];
        const eventCount = await calendarSync({
            userId,
            email,
            token: accountData.accessToken,
            refreshToken: accountData.refreshToken,
            familyId: userData.familyId
        });

        logger.info("Manual sync completed successfully", {userId, eventCount});

        return {
            success: true,
            eventCount,
            message: "Google calendar sync completed successfully"
        };
    } catch (error) {
        logger.error("Manual sync failed", error);
        throw new HttpsError('internal', error.message);
    }
});

exports.triggerMicrosoftSync = onCall({
    memory: '256MiB',
    enforceAppCheck: false,
    maxInstances: 10,
    region: 'us-central1',
    invoker: 'public',
}, async (request) => {
    if (!request.auth) {
        throw new HttpsError('unauthenticated', 'Authentication required');
    }

    try {
        const userId = request.auth.uid;
        const {email} = request.data;

        if (!email) {
            throw new HttpsError('invalid-argument', 'Email is required');
        }

        logger.info('Starting Microsoft sync', {userId, email});

        const userDoc = await db.collection("Profiles").doc(userId).get();
        if (!userDoc.exists) {
            throw new HttpsError('not-found', 'User profile not found');
        }

        const userData = userDoc.data();
        const accountData = userData.microsoftAccounts?.[email];

        if (!accountData) {
            throw new HttpsError('failed-precondition', 'Microsoft account not found');
        }

        let {accessToken, refreshToken} = accountData;

        // Try to refresh token if it exists
        if (refreshToken) {
            try {
                const refreshedTokens = await refreshMicrosoftToken(refreshToken);
                accessToken = refreshedTokens.accessToken;
                refreshToken = refreshedTokens.refreshToken || refreshToken;

                // Update the stored tokens
                await db.collection("Profiles").doc(userId).update({
                    [`microsoftAccounts.${email}`]: {
                        ...accountData,
                        accessToken,
                        refreshToken,
                        lastRefresh: FieldValue.serverTimestamp()
                    }
                });
            } catch (refreshError) {
                logger.error('Token refresh failed:', refreshError);
                throw new HttpsError(
                    'failed-precondition',
                    'Failed to refresh Microsoft token. Please reconnect your account.',
                    {requiresReauth: true}
                );
            }
        }

        try {
            const eventCount = await fetchAndSaveMicrosoftEvents({
                token: accessToken,
                refreshToken,
                email,
                familyId: userData.familyId,
                creatorId: userId
            });

            logger.info('Microsoft sync completed successfully', {eventCount});
            return {
                success: true,
                eventCount,
                message: "Microsoft calendar sync completed successfully"
            };
        } catch (syncError) {
            if (syncError.message?.includes('401') ||
                syncError.message?.includes('unauthorized') ||
                syncError.message?.includes('invalid_grant')) {
                throw new HttpsError(
                    'unauthenticated',
                    'Microsoft authentication expired. Please reconnect your account.',
                    {requiresReauth: true}
                );
            }
            throw new HttpsError('internal', syncError.message);
        }
    } catch (error) {
        logger.error('Microsoft sync function error:', error);
        if (error instanceof HttpsError) {
            throw error;
        }
        throw new HttpsError('internal', error.message || 'Unknown error occurred');
    }
});

exports.forceWatchRenewal = onRequest(async (req, res) => {
    const batch = db.batch();
    const watches = await db.collection('CalendarWatches').get();
    watches.forEach(doc => {
        batch.update(doc.ref, {
            expiration: 0
        });
    });
    await batch.commit();
    res.status(200).send('Forced renewal of all watches');
});

exports.deleteFamily = functions.https.onCall(async (data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
    }

    const { familyId } = data;

    if (!familyId) {
        throw new functions.https.HttpsError('invalid-argument', 'Family ID is required');
    }

    try {
        const db = admin.firestore();

        const requestingUserProfile = await db.collection('Profiles')
            .doc(context.auth.uid)
            .get();

        if (!requestingUserProfile.exists || requestingUserProfile.data().userType !== 'parent') {
            throw new functions.https.HttpsError('permission-denied', 'Only parents can delete families');
        }

        if (requestingUserProfile.data().familyId !== familyId) {
            throw new functions.https.HttpsError('permission-denied', 'You can not delete other families');
        }

        const profilesSnapshot = await db.collection('Profiles')
            .where('familyId', '==', familyId)
            .get();

        const batch = db.batch();
        const profileIds = [];

        for (const profile of profilesSnapshot.docs) {
            const userId = profile.id;
            profileIds.push(userId);

            const collections = [
                'BrainDumps',
                'Groceries',
                'Todos',
                'Events',
                //'Feedbacks'
            ];

            for (const collectionName of collections) {
                const userDocsSnapshot = await db.collection(collectionName)
                    .where('creatorId', '==', userId)
                    .get();

                userDocsSnapshot.docs.forEach(doc => {
                    batch.delete(doc.ref);
                });
            }

            batch.delete(profile.ref);
        }

        const householdSnapshot = await db.collection('Households')
            .where('familyId', '==', familyId)
            .get();

        if (!householdSnapshot.empty) {
            const householdDoc = householdSnapshot.docs[0];
            batch.delete(householdDoc.ref);
        } else {
            console.log('Household not found for familyId:', familyId);
        }

        await batch.commit();

        // Delete Firebase Auth accounts
        await Promise.all(profileIds.map(userId =>
            admin.auth().deleteUser(userId)
        ));

        return { success: true, message: 'Family deleted successfully' };

    } catch (error) {
        console.error('Error deleting family:', error);
        throw new functions.https.HttpsError('internal', 'Error deleting family data');
    }
});