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

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

let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
let notificationTimeout = null;
let eventCount = 0;
let notificationInProgress = false;

const GOOGLE_CALENDAR_ID = "primary";
const CHANNEL_ID = "cally-family-calendar";
const WEBHOOK_URL = "https://us-central1-cally-family-calendar.cloudfunctions.net/sendSyncNotification";

async function getPushTokensForFamily(familyId, excludeUserId = null) {
    const usersRef = db.collection('Profiles');
    const snapshot = await usersRef.where('familyId', '==', familyId).get();
    let pushTokens = [];

    console.log('Getting push tokens:', {
        familyId,
        excludeUserId
    });

    snapshot.forEach(doc => {
        const data = doc.data();
        const userId = doc.id;

        console.log('Processing user:', {
            docId: userId,
            hasToken: !!data.pushToken,
            excluded: userId === excludeUserId
        });

        if (userId !== excludeUserId && data.pushToken) {
            console.log('Including token for user:', {
                userId,
                excludeUserId
            });
            pushTokens.push(data.pushToken);
        } else {
            console.log('Excluding token for user:', {
                userId,
                excludeUserId
            });
        }
    });

    return pushTokens;
}

exports.sendOverviews = functions.pubsub
    .schedule('0 20 * * *')
    .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')
    .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.sendNotificationOnEventCreation = functions.firestore
    .document('Events/{eventId}')
    .onCreate(async (snapshot, context) => {
        const eventData = snapshot.data();
        const familyId = eventData.familyId;
        const creatorId = eventData.creatorId;
        const title = eventData.title || '';
        const externalOrigin = eventData.externalOrigin || false;
        const eventId = context.params.eventId;

        if (!familyId || !creatorId) {
            console.error('Missing familyId or creatorId in event data');
            return;
        }

        const timeWindow = Math.floor(Date.now() / 5000);
        const batchId = `${timeWindow}_${familyId}_${creatorId}_${externalOrigin ? 'sync' : 'manual'}`;
        const batchRef = admin.firestore().collection('EventBatches').doc(batchId);

        try {
            await admin.firestore().runTransaction(async (transaction) => {
                const batchDoc = await transaction.get(batchRef);

                if (!batchDoc.exists) {
                    transaction.set(batchRef, {
                        familyId,
                        creatorId,
                        externalOrigin: externalOrigin || false,
                        events: [{
                            id: eventId,
                            title: title || '',
                            timestamp: new Date().toISOString()  // Using ISO string instead of serverTimestamp
                        }],
                        createdAt: admin.firestore.FieldValue.serverTimestamp(),
                        processed: false,
                        expiresAt: new Date(Date.now() + 10000)
                    });
                } else {
                    const existingEvents = batchDoc.data().events || [];
                    transaction.update(batchRef, {
                        events: [...existingEvents, {
                            id: eventId,
                            title: title || '',
                            timestamp: new Date().toISOString()  // Using ISO string instead of serverTimestamp
                        }]
                    });
                }
            });

            console.log(`[HOUSEHOLD_UPDATE] Event created - Processing timestamp updates`, {
                eventId,
                familyId,
                eventTitle: title || 'Untitled'
            });

            const householdsSnapshot = await admin.firestore().collection('Households')
                .where('familyId', '==', familyId)
                .get();

            console.log(`[HOUSEHOLD_UPDATE] Found ${householdsSnapshot.size} households to update for family ${familyId}`);

            const householdBatch = admin.firestore().batch();
            householdsSnapshot.docs.forEach((doc) => {
                console.log(`[HOUSEHOLD_UPDATE] Adding household ${doc.id} to update batch`);
                householdBatch.update(doc.ref, {
                    lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp()
                });
            });

            const batchStartTime = Date.now();
            await householdBatch.commit();
            console.log(`[HOUSEHOLD_UPDATE] Batch update completed in ${Date.now() - batchStartTime}ms`, {
                familyId,
                householdsUpdated: householdsSnapshot.size,
                eventId
            });

        } catch (error) {
            console.error('Error in event creation handler:', error);
            throw error;
        }
    });



exports.processEventBatches = functions.pubsub
    .schedule('every 1 minutes')
    .onRun(async (context) => {
        const batchesRef = admin.firestore().collection('EventBatches');
        const now = admin.firestore.Timestamp.fromDate(new Date());
        const snapshot = await batchesRef
            .where('processed', '==', false)
            .where('expiresAt', '<=', now)
            .limit(100)
            .get();

        if (snapshot.empty) return null;

        const processPromises = snapshot.docs.map(async (doc) => {
            const batchData = doc.data();
            const {familyId, creatorId, externalOrigin, events} = batchData;

            console.log('Processing batch:', {
                batchId: doc.id,
                creatorId,
                familyId
            });

            try {
                const pushTokens = await getPushTokensForFamily(
                    familyId,
                    creatorId
                );

                // Add logging to see what tokens are returned
                console.log('Push tokens retrieved:', {
                    batchId: doc.id,
                    tokenCount: pushTokens.length,
                    tokens: pushTokens
                });

                if (pushTokens.length) {

                    let notificationMessage;
                    if (externalOrigin) {
                        notificationMessage = `Calendar sync completed: ${events.length} ${events.length === 1 ? 'event has' : 'events have'} been added.`;
                    } else {
                        notificationMessage = events.length === 1
                            ? `New event "${events[0].title}" has been added to the family calendar.`
                            : `${events.length} new events have been added to the family calendar.`;
                    }

                    await sendNotifications(pushTokens, {
                        title: 'New Family Calendar Events',
                        body: notificationMessage,
                        data: {
                            type: externalOrigin ? 'sync' : 'manual',
                            count: events.length
                        }
                    });

                    await storeNotification({
                        type: externalOrigin ? 'sync' : 'manual',
                        familyId,
                        content: notificationMessage,
                        timestamp: admin.firestore.FieldValue.serverTimestamp(),
                        creatorId,
                        eventCount: events.length
                    });
                }

                await doc.ref.update({
                    processed: true,
                    processedAt: admin.firestore.FieldValue.serverTimestamp()
                });

            } catch (error) {
                console.error(`Error processing event batch ${doc.id}:`, error);
                await doc.ref.update({
                    processed: true,
                    processedAt: admin.firestore.FieldValue.serverTimestamp(),
                    error: error.message
                });
            }
        });

        await Promise.all(processPromises);
    });

async function addToUpdateBatch(eventData, eventId) {
    const timeWindow = Math.floor(Date.now() / 2000);
    const batchId = `${timeWindow}_${eventData.familyId}_${eventData.lastModifiedBy}`;
    const batchRef = admin.firestore().collection('UpdateBatches').doc(batchId);

    try {
        await admin.firestore().runTransaction(async (transaction) => {
            const batchDoc = await transaction.get(batchRef);

            if (!batchDoc.exists) {
                transaction.set(batchRef, {
                    familyId: eventData.familyId,
                    lastModifiedBy: eventData.lastModifiedBy,
                    externalOrigin: eventData.externalOrigin,
                    events: [{
                        id: eventId,
                        title: eventData.title,
                        startDate: eventData.startDate
                    }],
                    createdAt: admin.firestore.FieldValue.serverTimestamp(),
                    processed: false,
                    expiresAt: new Date(Date.now() + 3000)
                });
            } else {
                const existingEvents = batchDoc.data().events || [];
                transaction.update(batchRef, {
                    events: [...existingEvents, {
                        id: eventId,
                        title: eventData.title,
                        startDate: eventData.startDate
                    }]
                });
            }
        });
    } catch (error) {
        console.error('Error adding to update batch:', error);
        throw error;
    }
}

exports.cleanupEventBatches = functions.pubsub
    .schedule('every 24 hours')
    .onRun(async (context) => {
        const batchesRef = admin.firestore().collection('EventBatches');
        const dayAgo = admin.firestore.Timestamp.fromDate(new Date(Date.now() - 24 * 60 * 60 * 1000));

        while (true) {
            const oldBatches = await batchesRef
                .where('processedAt', '<=', dayAgo)
                .limit(500)
                .get();

            if (oldBatches.empty) break;

            const batch = admin.firestore().batch();
            oldBatches.docs.forEach(doc => batch.delete(doc.ref));
            await batch.commit();
        }
    });

exports.processUpdateBatches = functions.pubsub
    .schedule('every 1 minutes')
    .onRun(async (context) => {
        const batchesRef = admin.firestore().collection('UpdateBatches');


        const now = admin.firestore.Timestamp.fromDate(new Date());
        const snapshot = await batchesRef
            .where('processed', '==', false)
            .where('expiresAt', '<=', now)
            .limit(100)
            .get();

        const processPromises = snapshot.docs.map(async (doc) => {
            const batchData = doc.data();

            try {

                const pushTokens = await getPushTokensForFamily(
                    batchData.familyId,
                    batchData.lastModifiedBy
                );

                if (pushTokens.length) {
                    let message;
                    if (batchData.externalOrigin) {
                        message = `Calendar sync completed: ${batchData.events.length} events have been updated`;
                    } else {
                        message = batchData.events.length === 1
                            ? `Event "${batchData.events[0].title}" has been updated`
                            : `${batchData.events.length} events have been updated`;
                    }


                    await admin.firestore().runTransaction(async (transaction) => {

                        await sendNotifications(pushTokens, {
                            title: batchData.externalOrigin ? "Calendar Sync Complete" : "Events Updated",
                            body: message,
                            data: {
                                type: 'event_update',
                                count: batchData.events.length
                            }
                        });


                        const notificationRef = admin.firestore().collection('Notifications').doc();
                        transaction.set(notificationRef, {
                            type: 'event_update',
                            familyId: batchData.familyId,
                            content: message,
                            excludedUser: batchData.lastModifiedBy,
                            timestamp: admin.firestore.FieldValue.serverTimestamp(),
                            count: batchData.events.length,
                            date: batchData.events[0].startDate
                        });


                        transaction.update(doc.ref, {
                            processed: true,
                            processedAt: admin.firestore.FieldValue.serverTimestamp()
                        });
                    });
                } else {

                    await doc.ref.update({
                        processed: true,
                        processedAt: admin.firestore.FieldValue.serverTimestamp()
                    });
                }
            } catch (error) {
                console.error(`Error processing batch ${doc.id}:`, error);

                await doc.ref.update({
                    processed: true,
                    processedAt: admin.firestore.FieldValue.serverTimestamp(),
                    error: error.message
                });
            }
        });

        await Promise.all(processPromises);
    });

exports.cleanupUpdateBatches = functions.pubsub
    .schedule('every 24 hours')
    .onRun(async (context) => {
        const batchesRef = admin.firestore().collection('UpdateBatches');
        const dayAgo = admin.firestore.Timestamp.fromDate(new Date(Date.now() - 24 * 60 * 60 * 1000));

        while (true) {
            const oldBatches = await batchesRef
                .where('processedAt', '<=', dayAgo)
                .limit(500)
                .get();

            if (oldBatches.empty) break;

            const batch = admin.firestore().batch();
            oldBatches.docs.forEach(doc => batch.delete(doc.ref));
            await batch.commit();
        }
    });

exports.checkUpcomingEvents = functions.pubsub
    .schedule('every 5 minutes')
    .onRun(async (context) => {
        const now = admin.firestore.Timestamp.now();
        const eventsSnapshot = await admin.firestore()
            .collection('Events')
            .where('startDate', '>=', now)
            .get();

        const processPromises = eventsSnapshot.docs.map(async (doc) => {
            const event = doc.data();
            if (!event?.startDate) return;

            const {familyId, title, allDay} = event;
            try {
                const familyDoc = await admin.firestore().collection('Families').doc(familyId).get();
                if (!familyDoc.exists) return;

                const familySettings = familyDoc.data()?.settings || {};
                const reminderTime = familySettings.defaultReminderTime || 15;
                const eventTime = event.startDate.toDate();
                const reminderThreshold = new Date(now.toDate().getTime() + (reminderTime * 60 * 1000));

                if (allDay) {
                    const eveningBefore = new Date(eventTime);
                    eveningBefore.setDate(eveningBefore.getDate() - 1);
                    eveningBefore.setHours(20, 0, 0, 0);

                    if (now.toDate() >= eveningBefore && !event.eveningReminderSent) {
                        const pushTokens = await getPushTokensForFamily(familyId);
                        if (pushTokens.length) {
                            await sendNotifications(pushTokens, {
                                title: "Tomorrow's All-Day Event",
                                body: `Tomorrow: ${title}`,
                                data: {type: 'event_reminder', eventId: doc.id}
                            });
                            await doc.ref.update({eveningReminderSent: true});
                        }
                    }
                } else if (eventTime <= reminderThreshold && !event.reminderSent) {
                    const pushTokens = await getPushTokensForFamily(familyId);
                    if (pushTokens.length) {
                        await sendNotifications(pushTokens, {
                            title: "Upcoming Event",
                            body: `In ${reminderTime} minutes: ${title}`,
                            data: {type: 'event_reminder', eventId: doc.id}
                        });
                        await doc.ref.update({reminderSent: true});
                    }
                }
            } catch (error) {
                console.error(`Error processing reminder for event ${doc.id}:`, error);
            }
        });

        await Promise.all(processPromises);
    });

async function storeNotification(notificationData) {
    try {
        await admin.firestore()
            .collection('Notifications')
            .add(notificationData);
    } catch (error) {
        console.error('Error storing notification:', error);
    }
}

async function sendNotifications(pushTokens, notification) {
    if (!pushTokens.length) return;

    const messages = pushTokens
        .filter(token => Expo.isExpoPushToken(token))
        .map(pushToken => ({
            to: pushToken,
            sound: 'default',
            priority: 'high',
            ...notification
        }));

    const chunks = expo.chunkPushNotifications(messages);

    for (let chunk of chunks) {
        try {
            const tickets = await expo.sendPushNotificationsAsync(chunk);
            for (let ticket of tickets) {
                if (ticket.status === "error") {
                    if (ticket.details?.error === "DeviceNotRegistered") {
                        await removeInvalidPushToken(ticket.to);
                    }
                    console.error('Push notification error:', ticket.message);
                }
            }
        } catch (error) {
            console.error('Error sending notifications:', error);
        }
    }
}

exports.createSubUser = onRequest(async (request, response) => {
    const authHeader = request.get('Authorization');

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        logger.warn("Missing or incorrect Authorization header", {authHeader});
        response.status(401).json({error: 'Unauthorized'});
        return;
    }

    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});
            response.status(401).json({error: 'Unauthorized: Invalid token'});
            return;
        }

        logger.info("Processing user creation", {requestBody: request.body.data});

        const {userType, firstName, lastName, email, password, familyId} = request.body.data;

        if (!email || !password || !firstName || !userType || !familyId) {
            logger.warn("Missing required fields in request body", {requestBody: request.body.data});
            response.status(400).json({error: "Missing required fields"});
            return;
        }

        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});
            response.status(500).json({error: "Failed to create user"});
            return;
        }

        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});
            response.status(500).json({error: "Failed to save user profile"});
            return;
        }

        response.status(200).json({
            data: {
                message: "User created successfully", userId: userRecord.uid,
            }
        });
    } catch (error) {
        logger.error("Error in createSubUser function", {error: error.message});
        response.status(500).json({data: {error: error.message}});
    }
});

exports.removeSubUser = onRequest(async (request, response) => {
    const authHeader = request.get('Authorization');

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        logger.warn("Missing or incorrect Authorization header", {authHeader});
        response.status(401).json({error: 'Unauthorized'});
        return;
    }

    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});
            response.status(401).json({error: 'Unauthorized: Invalid token'});
            return;
        }

        logger.info("Processing user removal", {requestBody: request.body.data});

        const {userId, familyId} = request.body.data;

        if (!userId || !familyId) {
            logger.warn("Missing required fields in request body", {requestBody: request.body.data});
            response.status(400).json({error: "Missing required fields"});
            return;
        }

        try {
            const userProfile = await getFirestore()
                .collection("Profiles")
                .doc(userId)
                .get();

            if (!userProfile.exists) {
                logger.error("User profile not found", {userId});
                response.status(404).json({error: "User not found"});
                return;
            }

            if (userProfile.data().familyId !== familyId) {
                logger.error("User does not belong to the specified family", {
                    userId,
                    requestedFamilyId: familyId,
                    actualFamilyId: userProfile.data().familyId
                });
                response.status(403).json({error: "User does not belong to the specified family"});
                return;
            }

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

            response.status(200).json({
                data: {
                    message: "User removed successfully",
                    success: true
                }
            });
        } catch (error) {
            logger.error("Failed to remove user", {error: error.message});
            response.status(500).json({error: "Failed to remove user"});
            return;
        }
    } catch (error) {
        logger.error("Error in removeSubUser function", {error: error.message});
        response.status(500).json({data: {error: error.message}});
    }
});

exports.generateCustomToken = onRequest(async (request, response) => {
    try {
        const {userId} = request.body.data;

        console.log("Generating custom token for userId", {userId});

        if (!userId) {
            response.status(400).json({error: 'Missing userId'});
            return;
        }

        const customToken = await getAuth().createCustomToken(userId);
        response.status(200).json({data: {token: customToken}});
    } catch (error) {
        console.error("Error generating custom token", {error: error.message});
        response.status(500).json({error: "Failed to generate custom token"});
    }
});

exports.refreshTokens = functions.pubsub.schedule('every 45 minutes').onRun(async (context) => {
    console.log('Running token refresh job...');

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

    profilesSnapshot.forEach(async (profileDoc) => {
        const profileData = profileDoc.data();

        if (profileData.googleAccounts) {
            try {
                for (const googleEmail of Object.keys(profileData?.googleAccounts)) {
                    const googleToken = profileData?.googleAccounts?.[googleEmail]?.refreshToken;
                    if (googleToken) {
                        const {refreshedGoogleToken, refreshedRefreshToken} = await refreshGoogleToken(googleToken);
                        const updatedGoogleAccounts = {
                            ...profileData.googleAccounts,
                            [googleEmail]: {accessToken: refreshedGoogleToken, refreshToken: refreshedRefreshToken}
                        };
                        await profileDoc.ref.update({googleAccounts: updatedGoogleAccounts});
                        console.log(`Google token updated for user ${profileDoc.id}`);
                    }
                }
            } catch (error) {
                console.error(`Error refreshing Google token for user ${profileDoc.id}:`, error.message);
            }
        }

        if (profileData.microsoftAccounts) {
            try {
                for (const microsoftEmail of Object.keys(profileData?.microsoftAccounts)) {
                    const microsoftToken = profileData?.microsoftAccounts?.[microsoftEmail];
                    if (microsoftToken) {
                        const refreshedMicrosoftToken = await refreshMicrosoftToken(microsoftToken);
                        const updatedMicrosoftAccounts = {
                            ...profileData.microsoftAccounts,
                            [microsoftEmail]: refreshedMicrosoftToken
                        };
                        await profileDoc.ref.update({microsoftAccounts: updatedMicrosoftAccounts});
                        console.log(`Microsoft token updated for user ${profileDoc.id}`);
                    }
                }
            } catch (error) {
                console.error(`Error refreshing Microsoft token for user ${profileDoc.id}:`, error.message);
            }
        }

        if (profileData.appleAccounts) {
            try {
                for (const appleEmail of Object.keys(profileData?.appleAccounts)) {
                    const appleToken = profileData?.appleAccounts?.[appleEmail];
                    const refreshedAppleToken = await refreshAppleToken(appleToken);
                    const updatedAppleAccounts = {...profileData.appleAccounts, [appleEmail]: refreshedAppleToken};
                    await profileDoc.ref.update({appleAccunts: updatedAppleAccounts});
                    console.log(`Apple token updated for user ${profileDoc.id}`);
                }
            } catch (error) {
                console.error(`Error refreshing Apple token for user ${profileDoc.id}:`, error.message);
            }
        }
    });

    return null;
});

async function refreshMicrosoftToken(refreshToken) {
    try {
        const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
            grant_type: 'refresh_token',
            refresh_token: refreshToken,
            client_id: "13c79071-1066-40a9-9f71-b8c4b138b4af",
            scope: "openid profile email offline_access Calendars.ReadWrite User.Read",
        });

        return response.data.access_token;
    } catch (error) {
        console.error("Error refreshing Microsoft token:", error);
        throw error;
    }
}

async function getPushTokensForEvent() {
    const usersRef = db.collection('Profiles');
    const snapshot = await usersRef.get();
    let pushTokens = [];

    snapshot.forEach(doc => {
        const data = doc.data();
        if (data.pushToken) {
            pushTokens.push(data.pushToken);
        }
    });

    console.log('Push Tokens:', pushTokens);
    return pushTokens;
}

async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
    const usersRef = db.collection('Profiles');
    const snapshot = await usersRef.where('familyId', '==', familyId).get();
    let pushTokens = [];

    snapshot.forEach(doc => {
        const data = doc.data();

        if (data.uid !== creatorId && data.pushToken) {
            pushTokens.push(data.pushToken);
        }
    });

    return pushTokens;
}

async function removeInvalidPushToken(pushToken) {
    try {
        const profilesRef = db.collection('Profiles');
        const snapshot = await profilesRef.where('pushToken', '==', pushToken).get();

        const batch = db.batch();
        snapshot.forEach(doc => {
            batch.update(doc.ref, {
                pushToken: admin.firestore.FieldValue.delete()
            });
        });

        await batch.commit();
        console.log(`Removed invalid push token: ${pushToken}`);
    } catch (error) {
        console.error('Error removing invalid push token:', error);
    }
}

const fetch = require("node-fetch");


async function refreshGoogleToken(refreshToken) {
    try {
        console.log("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();
            console.error("Error refreshing Google token:", errorData);
            throw new Error(`Failed to refresh Google token: ${errorData.error || response.statusText}`);
        }

        const data = await response.json();
        console.log("Google token refreshed successfully");


        return {
            refreshedGoogleToken: data.access_token,
            refreshedRefreshToken: data.refresh_token || refreshToken,
        };
    } catch (error) {
        console.error("Error refreshing Google token:", error.message);
        throw error;
    }
}


async function getGoogleAccessTokens() {
    console.log("Fetching Google access tokens for all users...");
    const tokens = {};
    const profilesSnapshot = await db.collection("Profiles").get();

    await Promise.all(
        profilesSnapshot.docs.map(async (doc) => {
            const profileData = doc.data();
            const googleAccounts = profileData?.googleAccounts || {};

            for (const googleEmail of Object.keys(googleAccounts)) {

                const accountInfo = googleAccounts[googleEmail];
                const refreshToken = accountInfo?.refreshToken;

                if (refreshToken) {
                    try {
                        console.log(`Refreshing token for user ${doc.id} (email: ${googleEmail})`);
                        const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
                        tokens[doc.id] = accessToken;
                        console.log(`Token refreshed successfully for user ${doc.id}`);
                    } catch (error) {
                        tokens[doc.id] = accountInfo?.accessToken;
                        console.error(`Failed to refresh token for user ${doc.id}:`, error.message);
                    }
                } else {
                    console.log(`No refresh token available for user ${doc.id} (email: ${googleEmail})`);
                }
            }
        })
    );

    console.log("Access tokens fetched and refreshed as needed");
    return tokens;
}

exports.renewGoogleCalendarWatch = functions.pubsub
    .schedule("every 10 minutes")
    .onRun(async (context) => {
        console.log("Starting calendar watch renewal check");


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


        const existingWatches = await db.collection('CalendarWatches').get();
        const now = Date.now();


        const watchMap = new Map();
        existingWatches.forEach(doc => {
            watchMap.set(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);


            if (!userData.googleAccounts || Object.keys(userData.googleAccounts).length === 0) {
                continue;
            }


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

            if (!token) {
                console.log(`No token found for user ${userId}`);
                continue;
            }


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

            if (!needsRenewal) {
                continue;
            }

            try {
                console.log(`${existingWatch ? 'Renewing' : 'Creating new'} watch for user ${userId}`);

                const url = `https://www.googleapis.com/calendar/v3/calendars/${GOOGLE_CALENDAR_ID}/events/watch`;

                const response = await fetch(url, {
                    method: "POST",
                    headers: {
                        Authorization: `Bearer ${token}`,
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                        id: `${CHANNEL_ID}-${userId}`,
                        type: "web_hook",
                        address: `${WEBHOOK_URL}?userId=${userId}`,
                        params: {ttl: "604800"},
                    }),
                });

                if (!response.ok) {
                    throw new Error(await response.text());
                }

                const result = await response.json();

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

                console.log(`Successfully ${existingWatch ? 'renewed' : 'created'} watch for user ${userId}`);

            } catch (error) {
                console.error(`Failed to ${existingWatch ? 'renew' : 'create'} watch for user ${userId}:`, error);
                batch.set(db.collection('CalendarWatchErrors').doc(), {
                    userId,
                    error: error.message,
                    timestamp: admin.firestore.FieldValue.serverTimestamp(),
                });
            }
        }

        if (processedCount > 0) {
            await batch.commit();
        }

        console.log(`Completed calendar watch processing:`, {
            totalProcessed: processedCount,
            newWatches: newWatchCount,
            renewals: renewalCount
        });
    });

const tokenRefreshInProgress = new Map();

exports.cleanupTokenRefreshFlags = functions.pubsub
    .schedule('every 5 minutes')
    .onRun(() => {
        tokenRefreshInProgress.clear();
        console.log('[CLEANUP] Cleared all token refresh flags');
        return null;
    });


async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, creatorId}) {
    const baseDate = new Date();
    const timeMin = new Date(baseDate.setMonth(baseDate.getMonth() - 1)).toISOString();
    const timeMax = new Date(baseDate.setMonth(baseDate.getMonth() + 2)).toISOString();

    let totalEvents = 0;
    let pageToken = null;
    const batchSize = 50;

    try {
        console.log(`[FETCH] Starting event fetch for user: ${email}`);

        do {
            let events = [];
            const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/primary/events`);
            url.searchParams.set("singleEvents", "true");
            url.searchParams.set("timeMin", timeMin);
            url.searchParams.set("timeMax", timeMax);
            url.searchParams.set("maxResults", batchSize.toString());
            if (pageToken) url.searchParams.set("pageToken", pageToken);

            console.log(`[FETCH] Making request with token: ${token.substring(0, 10)}...`);

            let response = await fetch(url.toString(), {
                headers: {
                    Authorization: `Bearer ${token}`,
                },
            });

            if (response.status === 401 && refreshToken) {
                console.log(`[TOKEN] Token expired during fetch, refreshing for ${email}`);
                const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
                if (refreshedGoogleToken) {
                    console.log(`[TOKEN] Token refreshed successfully during fetch`);
                    token = refreshedGoogleToken;

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

                    // Retry the request with new token
                    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}`);
            }

            data.items?.forEach((item) => {
                const googleEvent = {
                    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",
                };
                events.push(googleEvent);
            });

            if (events.length > 0) {
                console.log(`[FETCH] Saving batch of ${events.length} events`);
                await saveEventsToFirestore(events);
                totalEvents += events.length;
            }

            pageToken = data.nextPageToken;
        } while (pageToken);

        console.log(`[FETCH] Completed with ${totalEvents} total events`);
        return totalEvents;
    } catch (error) {
        console.error(`[ERROR] Failed fetching events for ${email}:`, error);
        throw error;
    }
}

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 calendarSync({userId, email, token, refreshToken, familyId}) {
    console.log(`[SYNC] Starting calendar sync for user ${userId} with email ${email}`);
    try {
        if (refreshToken) {
            console.log(`[TOKEN] Initial token refresh for ${email}`);
            const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
            if (refreshedGoogleToken) {
                console.log(`[TOKEN] Token refreshed successfully for ${email}`);
                token = refreshedGoogleToken;
                await db.collection("Profiles").doc(userId).update({
                    [`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
                });
            }
        }

        const eventCount = await fetchAndSaveGoogleEvents({
            token,
            refreshToken,
            email,
            familyId,
            creatorId: userId,
        });

        console.log(`[SYNC] Calendar sync completed. Processed ${eventCount} events`);
        return eventCount;
    } catch (error) {
        console.error(`[ERROR] Calendar sync failed for user ${userId}:`, error);
        throw error;
    }
}

exports.sendSyncNotification = functions.https.onRequest(async (req, res) => {
    const userId = req.query.userId;
    const calendarId = req.body.resourceId;

    console.log(`[SYNC START] Received notification for user ${userId} with calendar ID ${calendarId}`);
    console.log('Request headers:', req.headers);
    console.log('Request body:', req.body);

    try {
        console.log(`[PROFILE] Fetching user profile for ${userId}`);
        const userDoc = await db.collection("Profiles").doc(userId).get();

        if (!userDoc.exists) {
            console.error(`[ERROR] No profile found for user ${userId}`);
            return res.status(404).send("User profile not found");
        }

        const userData = userDoc.data();
        console.log(`[PROFILE] Found profile data for user ${userId}:`, {
            hasGoogleAccounts: !!userData.googleAccounts,
            familyId: userData.familyId
        });

        const {googleAccounts} = userData;
        const email = Object.keys(googleAccounts || {})[0];

        if (!email) {
            console.error(`[ERROR] No Google account found for user ${userId}`);
            return res.status(400).send("No Google account found");
        }

        console.log(`[GOOGLE] Using Google account: ${email}`);

        const accountData = googleAccounts[email] || {};
        const token = accountData.accessToken;
        const refreshToken = accountData.refreshToken;
        const familyId = userData.familyId;

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

        console.log(`[SYNC] Starting calendar sync for user ${userId} (family: ${familyId})`);
        const syncStartTime = Date.now();
        await calendarSync({userId, email, token, refreshToken, familyId});
        console.log(`[SYNC] Calendar sync completed in ${Date.now() - syncStartTime}ms`);

        console.log(`[HOUSEHOLDS] Fetching households for family ${familyId}`);
        const querySnapshot = await db.collection('Households')
            .where("familyId", "==", familyId)
            .get();

        console.log(`[HOUSEHOLDS] Found ${querySnapshot.size} households to update`);

        const batch = db.batch();
        querySnapshot.docs.forEach((doc) => {
            console.log(`[HOUSEHOLDS] Adding household ${doc.id} to update batch`);
            batch.update(doc.ref, {
                lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp()
            });
        });

        console.log(`[HOUSEHOLDS] Committing batch update for ${querySnapshot.size} households`);
        const batchStartTime = Date.now();
        await batch.commit();
        console.log(`[HOUSEHOLDS] Batch update completed in ${Date.now() - batchStartTime}ms`);

        console.log(`[SYNC COMPLETE] Successfully processed sync for user ${userId}`);
        res.status(200).send("Sync completed successfully.");
    } catch (error) {
        console.error(`[ERROR] Error in sendSyncNotification for user ${userId}:`, {
            errorMessage: error.message,
            errorStack: error.stack,
            errorCode: error.code
        });
        res.status(500).send("Failed to send sync notification.");
    }
});


async function refreshMicrosoftToken(refreshToken) {
    try {
        console.log("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();
            console.error("Error refreshing Microsoft token:", errorData);
            throw new Error(`Failed to refresh Microsoft token: ${errorData.error || response.statusText}`);
        }

        const data = await response.json();
        console.log("Microsoft token refreshed successfully");

        return {
            accessToken: data.access_token,
            refreshToken: data.refresh_token || refreshToken
        };
    } catch (error) {
        console.error("Error refreshing Microsoft token:", error.message);
        throw error;
    }
}

async function fetchAndSaveMicrosoftEvents({token, refreshToken, email, familyId, creatorId}) {
    const baseDate = new Date();
    const timeMin = new Date(baseDate.setMonth(baseDate.getMonth() - 1)).toISOString();
    const timeMax = new Date(baseDate.setMonth(baseDate.getMonth() + 2)).toISOString();

    try {
        console.log(`Fetching Microsoft calendar 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}'`
        });

        const response = await fetch(`${url}?${queryParams}`, {
            headers: {
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json'
            }
        });

        const data = await response.json();

        if (response.status === 401 && refreshToken) {
            console.log(`Token expired for user: ${email}, attempting to refresh`);
            const {accessToken: newToken} = await refreshMicrosoftToken(refreshToken);
            if (newToken) {
                return fetchAndSaveMicrosoftEvents({
                    token: newToken,
                    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",
            };
        });

        console.log(`Saving Microsoft calendar events to Firestore for user: ${email}`);
        await saveEventsToFirestore(events);

        return events.length;

    } catch (error) {
        console.error(`Error fetching Microsoft Calendar events for ${email}:`, error);
        throw error;
    }
}

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: admin.firestore.FieldValue.serverTimestamp()
                }, {merge: true});

            } catch (error) {
                console.error(`Failed to renew Microsoft subscription for ${userId}:`, error);
                batch.set(db.collection('MicrosoftSubscriptionErrors').doc(), {
                    userId,
                    error: error.message,
                    timestamp: admin.firestore.FieldValue.serverTimestamp()
                });
            }
        }

        await batch.commit();
    });

exports.microsoftCalendarWebhook = functions.https.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
        });

        res.status(200).send();
    } catch (error) {
        console.error(`Error processing Microsoft webhook for ${userId}:`, error);
        res.status(500).send();
    }
});

exports.triggerGoogleSync = functions.https.onCall(async (data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError(
            'unauthenticated',
            'Authentication required'
        );
    }

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

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

        if (!userData?.googleAccounts?.[email]) {
            throw new functions.https.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
        });

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

exports.triggerMicrosoftSync = functions.https.onCall(async (data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError(
            'unauthenticated',
            'Authentication required'
        );
    }

    try {
        const {email} = data;
        if (!email) {
            throw new functions.https.HttpsError(
                'invalid-argument',
                'Email is required'
            );
        }

        console.log('Starting Microsoft sync for:', {userId: context.auth.uid, email});

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

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

        if (!accountData) {
            throw new functions.https.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(context.auth.uid).update({
                    [`microsoftAccounts.${email}`]: {
                        ...accountData,
                        accessToken,
                        refreshToken,
                        lastRefresh: admin.firestore.FieldValue.serverTimestamp()
                    }
                });
            } catch (refreshError) {
                console.error('Token refresh failed:', refreshError);
                throw new functions.https.HttpsError(
                    'failed-precondition',
                    'Failed to refresh Microsoft token. Please reconnect your account.',
                    {requiresReauth: true}
                );
            }
        } else if (!accessToken) {
            throw new functions.https.HttpsError(
                'failed-precondition',
                'Microsoft account requires authentication. Please reconnect your account.',
                {requiresReauth: true}
            );
        }

        try {
            console.log('Fetching Microsoft events with token');
            const eventCount = await fetchAndSaveMicrosoftEvents({
                token: accessToken,
                refreshToken,
                email,
                familyId: userData.familyId,
                creatorId: context.auth.uid
            });

            console.log('Microsoft sync completed successfully:', {eventCount});
            return {
                success: true,
                eventCount,
                message: "Microsoft calendar sync completed successfully"
            };
        } catch (syncError) {
            // Check if the error is due to invalid token
            if (syncError.message?.includes('401') ||
                syncError.message?.includes('unauthorized') ||
                syncError.message?.includes('invalid_grant')) {
                throw new functions.https.HttpsError(
                    'unauthenticated',
                    'Microsoft authentication expired. Please reconnect your account.',
                    {requiresReauth: true}
                );
            }
            throw new functions.https.HttpsError(
                'internal',
                syncError.message || 'Failed to sync Microsoft calendar',
                {originalError: syncError}
            );
        }
    } catch (error) {
        console.error('Microsoft sync function error:', error);
        if (error instanceof functions.https.HttpsError) {
            throw error;
        }
        throw new functions.https.HttpsError(
            'internal',
            error.message || 'Unknown error occurred',
            {originalError: error}
        );
    }
});

exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
    .document('Events/{eventId}')
    .onUpdate(async (change, context) => {
        const eventData = change.after.data();
        const familyId = eventData.familyId;
        const eventId = context.params.eventId;

        console.log(`[HOUSEHOLD_UPDATE] Event updated - Processing timestamp updates`, {
            eventId,
            familyId,
            eventTitle: eventData.title || 'Untitled'
        });

        try {
            const householdsSnapshot = await db.collection('Households')
                .where('familyId', '==', familyId)
                .get();

            console.log(`[HOUSEHOLD_UPDATE] Found ${householdsSnapshot.size} households to update for family ${familyId}`);

            const batch = db.batch();
            householdsSnapshot.docs.forEach((doc) => {
                console.log(`[HOUSEHOLD_UPDATE] Adding household ${doc.id} to update batch`);
                batch.update(doc.ref, {
                    lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp()
                });
            });

            const batchStartTime = Date.now();
            await batch.commit();
            console.log(`[HOUSEHOLD_UPDATE] Batch update completed in ${Date.now() - batchStartTime}ms`, {
                familyId,
                householdsUpdated: householdsSnapshot.size,
                eventId
            });

        } catch (error) {
            console.error(`[HOUSEHOLD_UPDATE] Error updating households for event update`, {
                eventId,
                familyId,
                error: error.message,
                stack: error.stack
            });
            throw error;
        }
    });
