import Constants from "expo-constants";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { SECURESTORE_NOTIFTOKEN, useAuth } from "./Auth/AuthContext";
import { queryKeys } from "./query-keys";
import * as Notifications from "expo-notifications";
import { getItemAsync } from "expo-secure-store";
import { AxiosRequestConfig } from "axios";
import { Platform } from "react-native";
import { navigate } from "../navigation/RootNavigator";

export interface UserNotification {
    id: string
    user: string
    title: string
    data: string
    read: boolean
}

export type NotifType = "near-landmark" | 'near-landmarks' | "landmark-like"

export const useNotificationToken = () => {
    const { sendApiRequestAsync, userId, accessToken } = useAuth();

    const ensureNotificationTokenExistsOnServer = async (notificationToken: string, retries: number) => {
        if (retries > 0) {
            try {
                const response = await sendApiRequestAsync({
                    axiosConfig: {
                        method: 'POST',
                        data: {token: notificationToken},
                        url: `/api/notif-token/${userId}/`,
                    },
                    authorized: true,
                    errorMessage: 'Something went wrong when checking notification on server'
                });   
                return response.data;
            } catch (error) {}
    
            await ensureNotificationTokenExistsOnServer(notificationToken, retries - 1)   
        }
        else {
            throw new Error("Could not validate notification token on server");
        }
    }
    
    const getNotificationToken = async () => {
        try {
            // first check storage
            let token = await getItemAsync(SECURESTORE_NOTIFTOKEN)
            if (token) {
                const tokenValidationResponse = await ensureNotificationTokenExistsOnServer(token, 3)
                if (tokenValidationResponse) {
                    return token
                }
            }

            // then check server
            const response = await sendApiRequestAsync({
                axiosConfig: {
                    method: 'GET',
                    url: `/api/notif-token/${userId}/`,
                },
                authorized: true,
                errorMessage: 'Something went wrong when retrieving notification token'
            });   
            
            if (response?.data) {
                return response?.data
            }

            token = (await Notifications.getExpoPushTokenAsync()).data;      
            
        } catch (error) {}
    }

    const {data: token} = useQuery<string, Error>(queryKeys.getNotificationToken, getNotificationToken, {
        enabled: !!userId && !!accessToken
    })

    return token
}

export const useNotifications = () => {
    const queryClient = useQueryClient();
    const {sendApiRequestAsync, accessToken, userId} = useAuth()

    const getNotifications = async () => {
        const response = await sendApiRequestAsync({
            axiosConfig: { 
                method: 'GET',
                url: `/api/user-profile/notifications/${userId}/`,
            }, 
            authorized: true,
            errorMessage: 'Something went wrong when retrieving notifications'});   
        return response?.data   
    }

    return useQuery<UserNotification[], Error>(queryKeys.getNotifications, getNotifications, {
        enabled: !!userId&& !!accessToken
    })
}

export const useMarkNotificationRead = () => {
    const queryClient = useQueryClient();
    const {sendApiRequestAsync} = useAuth()

    const markNotificationRead = async (notificationId: string) => {
        const response = await sendApiRequestAsync({
            axiosConfig: {
                method: 'POST',
                url: `/api/user-profile/notifications/mark-read/${notificationId}/`,
            }, 
            authorized: true,
            errorMessage: 'Something went wrong when marking notification as read'});   
        return response.data;
    }

    return useMutation(markNotificationRead, {
        onSuccess: () => { queryClient.invalidateQueries(queryKeys.getNotifications)},  
        onError: () => queryClient.invalidateQueries(queryKeys.getNotifications),  
    })
}

export const useDeleteNotification = () => {
    const queryClient = useQueryClient();
    const {sendApiRequestAsync} = useAuth()

    const removeNotificaiton = async (notificationId: string) => {
        if (notificationId) {        
            const response = await sendApiRequestAsync({
                axiosConfig: {
                    method: 'DELETE',
                    url: `/api/user-profile/notifications/delete/${notificationId}/`
                },
                authorized: true,
                errorMessage: 'Something went wrong when removing notification'
            });
            return response.data;
        }
    }

    return useMutation(removeNotificaiton, {
        onSuccess: () => { queryClient.invalidateQueries(queryKeys.getNotifications)},  
        onError: () => queryClient.invalidateQueries(queryKeys.getNotifications),  
    })
}

export const useRegisterNotifications = () => {
    const {setNotificationTokenAsync} = useAuth();
    const token = useNotificationToken()
    const {refetch: refetchNotifications} = useNotifications()
    const {mutateAsync: markNotificationRead} = useMarkNotificationRead()
    


    const registerNotificationsAsync = async () => {
        if (Constants.isDevice) {
            const { status: existingStatus } = await Notifications.getPermissionsAsync();
            let finalStatus = existingStatus;
            if (existingStatus !== 'granted') {
                const { status } = await Notifications.requestPermissionsAsync();
                finalStatus = status;
            }
            if (finalStatus !== 'granted') {
                alert('Failed to get push token for push notification!');
                return null;
            }

            return token
        } 
        else {
            console.warn('[Notifcations]: A physical device must be used for push notifications');
        }
        
        if (Platform.OS === 'android') {
            Notifications.setNotificationChannelAsync('default', {
                name: 'default',
                importance: Notifications.AndroidImportance.MAX,
                vibrationPattern: [0, 250, 250, 250],
                lightColor: '#FF231F7C',
            });
        }   

        return token
    };

    const handleNotificationInteraction = async (notifData: any) => {
        await markNotificationRead(notifData.notif_id)
        await refetchNotifications()
        if (notifData?.notif_type as NotifType == 'landmark-like' || notifData?.notif_type as NotifType == 'near-landmark')
            navigate('OutdoorMap', {selectedLandmark: notifData.landmark_id})
        if (notifData?.notif_type as NotifType == 'near-landmarks')
            navigate('OutdoorMap', {selectedLandmarks: notifData.landmarks})
    }

    const subscribeToNotifications = async () => {
        const token = await registerNotificationsAsync()
        if (token) {
            const notifReceivedSubscription = Notifications.addNotificationReceivedListener(async notification => { 
                await refetchNotifications()
            })
    
            const notifResponseReceivedSubscription = Notifications.addNotificationResponseReceivedListener(async response => {
                const notifData = response.notification.request.content.data
                handleNotificationInteraction(notifData)
            });
    
            return () => {
                notifReceivedSubscription.remove()
                notifResponseReceivedSubscription.remove()
            }; 
        }
    }

    return {subscribeToNotifications, handleNotificationInteraction}
}
