const {onRequest} = require("firebase-functions/v2/https");
const {getAuth} = require("firebase-admin/auth");
const {getFirestore} = 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 pushTokens = [];

exports.sendNotificationOnEventCreation = functions.firestore
    .document('Events/{eventId}')
    .onCreate(async (snapshot, context) => {
        const eventData = snapshot.data();
        const { familyId, creatorId, email } = eventData;

        if (email) {
            console.log('Event has an email field. Skipping notification.');
            return;
        }

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

        let pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);

        if (!pushTokens.length) {
            console.log('No push tokens available for the event.');
            return;
        }

        eventCount++;

        if (notificationTimeout) {
            clearTimeout(notificationTimeout);
        }

        notificationTimeout = setTimeout(async () => {
            const eventMessage = eventCount === 1
                ? `An event "${eventData.title}" has been added. Check it out!`
                : `${eventCount} new events have been added.`;

            let messages = pushTokens.map(pushToken => {
                if (!Expo.isExpoPushToken(pushToken)) {
                    console.error(`Push token ${pushToken} is not a valid Expo push token`);
                    return null;
                }

                return {
                    to: pushToken,
                    sound: 'default',
                    title: 'New Events Added!',
                    body: eventMessage,
                    data: { eventId: context.params.eventId },
                };
            }).filter(Boolean);

            let chunks = expo.chunkPushNotifications(messages);
            let tickets = [];

            for (let chunk of chunks) {
                try {
                    let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
                    tickets.push(...ticketChunk);

                    for (let ticket of ticketChunk) {
                        if (ticket.status === 'ok') {
                            console.log('Notification successfully sent:', ticket.id);
                        } else if (ticket.status === 'error') {
                            console.error(`Notification error: ${ticket.message}`);
                            if (ticket.details?.error === 'DeviceNotRegistered') {
                                await removeInvalidPushToken(ticket.to);
                            }
                        }
                    }
                } catch (error) {
                    console.error('Error sending notification:', error);
                }
            }

            eventCount = 0;
            pushTokens = [];

        }, 5000);
    });


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.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 1 hours').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 = await refreshGoogleToken(googleToken);
                        const updatedGoogleAccounts = {...profileData.googleAccounts, [googleEmail]: refreshedGoogleToken};
                        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 refreshGoogleToken(refreshToken) {
    try {
        const response = await axios.post('https://oauth2.googleapis.com/token', {
            grant_type: 'refresh_token',
            refresh_token: refreshToken,
            client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com", // Web client ID from googleConfig
        });

        return response.data.access_token; // Return the new access token
    } catch (error) {
        console.error("Error refreshing Google token:", error);
        throw error;
    }
}

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", // Client ID from microsoftConfig
            scope: "openid profile email offline_access Calendars.ReadWrite User.Read", // Scope from microsoftConfig
        });

        return response.data.access_token; // Return the new 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();
        // Exclude the creator
        if (data.uid !== creatorId && data.pushToken) {
            pushTokens.push(data.pushToken);
        }
    });

    return pushTokens;
}

async function removeInvalidPushToken(pushToken) {
    // TODO
}