import Constants from "expo-constants"
import React, { createContext, useContext, useState, useMemo, useEffect } from "react"
import { Platform } from "react-native"
import { checkMultiple, openSettings, Permission, PERMISSIONS, request, requestMultiple, RESULTS } from "react-native-permissions"
import * as Notifications from 'expo-notifications'
import { useAuth } from "./Auth/AuthContext"
import { useNotifications, useRegisterNotifications } from "./notifications"

interface PermissionsState {
    voicePermissionsGranted: boolean,
    checkVoicePermissions: () => Promise<boolean>,
    locationPermissionsGranted: boolean,
    checkLocationPermissions: () => Promise<boolean>,
    mediaPermissionsGranted: boolean,
    checkMediaPermissions: () => Promise<boolean>,
    notificationPermissionsGranted: boolean,
    permissionsLoading: boolean,
    
}

export type PermissionType = 'voice' | 'location' | 'media' | 'notification'

const PermissionsContext = createContext(null)

const permissionsMap = {
    android: {
        voice: [PERMISSIONS.ANDROID.RECORD_AUDIO],
        location: [PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION],
        media: [PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE],
        friendly: {
            voice: ['Microphone'],
            location: ['Location'],
            media: ['Camera', "Storage"],
        }
    },
    ios: {
        voice: [PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.SPEECH_RECOGNITION],
        location: [PERMISSIONS.IOS.LOCATION_ALWAYS, PERMISSIONS.IOS.LOCATION_WHEN_IN_USE],
        media: [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.PHOTO_LIBRARY],
        friendly: {
            voice: ['Microphone', 'Speech Recognition'],
            location: ['Location'],
            media: ['Camera', 'Storage']
        }
    }
}


export const PermissionsContextProvider: React.FC = ({children}) => {
    const {setAlert} = useAuth()

    const [voicePermissionsGranted, setVoicePermissions] = useState(false)
    const [locationPermissionsGranted, setLocationPermissions] = useState(false)
    const [mediaPermissionsGranted, setMediaPermissions] = useState(false)
    const [notificationPermissionsGranted, setNotificiationPermissions] = useState(false)
    const [permissionsLoading, setPermissionsLoading] = useState(false)
    const [permissionsFinishedLoading, setPermissionsFinisedLoading] = useState(false)

    const getPermissions = async (permission: PermissionType, setPermission: (state: boolean) => void) => {
        try {
            const permissions = permissionsMap[Platform.OS][permission]
            const checkResult = await checkMultiple(permissions)
            const requestResult = await requestMultiple(permissions.filter(p => checkResult[p] !== RESULTS.GRANTED))
            let granted = true;

            granted = Object.keys(requestResult).reduce((acc, permission) => acc && requestResult[permission] == RESULTS.GRANTED, granted)

            setPermission(granted)

            return granted
        } catch (error) {
            console.log('[Permissions]: An error occured when requesting permissions: ' + error)
            return false
        }
    }

    const checkLocationPermissions = async () => {
        try {
            return await getPermissions('location', setLocationPermissions);
        } catch (error) {
            console.log('[Permissions]: An error occured when getting voice permissions: ' + error)
            return false
        }
    }

    const checkMediaPermissions = async () => {
        try {
            return await getPermissions('media', setMediaPermissions);
        } catch (error) {
            console.log('[Permissions]: An error occured when getting media permissions: ' + error)
            return false
        }
    }

    const checkVoicePermissions = async () => {
        try {
            return await getPermissions('voice', setVoicePermissions);
        } catch (error) {
            console.log('[Permissions]: An error occured when getting voice permissions: ' + error)
            return false
        }
    }

    const checkNotificationPermissions = async () => {
        try {
            if (Constants.isDevice) {
                const { status: existingStatus } = await Notifications.getPermissionsAsync();
                let finalStatus = existingStatus;
                if (existingStatus !== 'granted') {
                    const { status } = await Notifications.requestPermissionsAsync();
                    finalStatus = status;
                }

                console.log('[Permissions]: Notification permissions: ' + finalStatus)
                setNotificiationPermissions(finalStatus === 'granted')
                return finalStatus === 'granted';
            } 
            else {
                console.warn('[Notifcations]: A physical device must be used for push notifications');
            }
        }
        catch {

        }
    }

    const createAlertForPermission = (permission: PermissionType) => {
        let permissionsMessage = ''
        permissionsMap[Platform.OS]['friendly'][permission].forEach((permissionLabel: string) => {
            permissionsMessage += `\n - ${permissionLabel}`
        })

        return permissionsMessage
    }

    const maybeShowPermissionsAlert = ({
        voicePermissionsGranted, 
        locationPermissionsGranted, 
        notificationPermissionsGranted}) => {
        let permissionsMessage = ''
        if (!voicePermissionsGranted) {
            permissionsMessage += createAlertForPermission('voice')
        }
        if (!locationPermissionsGranted) {
            permissionsMessage += createAlertForPermission('location')
        }
        if (!notificationPermissionsGranted) {
            permissionsMessage += `\n - Notifications`
        }
        if (permissionsMessage) {
            setAlert({
                title: 'Missing permissions',
                message: `You are missing some permissions which will limit features of the app: ${permissionsMessage} \n\nGo to settings and grant the permissions to gain access to these features`,                        
                type: 'warning',
                callback: () => openSettings(),
                callbackButtonText: 'Go to settings'
            })
        }
    };

    useEffect(() => {
        let isMounted = true
        const checkPermissionsOnLaunch = async () => {
            console.log('[Permissions]: Checking permissions on launch')
            const locationPermissionsGranted = await checkLocationPermissions()
            const voicePermissionsGranted = await checkVoicePermissions()
            const notificationPermissionsGranted = await checkNotificationPermissions()
            console.log('[Permissions]: Permissions checked on launch')
            return {
                voicePermissionsGranted: voicePermissionsGranted,
                locationPermissionsGranted: locationPermissionsGranted,
                notificationPermissionsGranted: notificationPermissionsGranted
            }
        }
        if (isMounted) {
            setPermissionsLoading(true)
            checkPermissionsOnLaunch().then(result => {
                setPermissionsLoading(false)
                setTimeout(() => {
                    maybeShowPermissionsAlert(result);
                }, 2000);
            })
    
        }

        return () => {
            isMounted = false
        }
    }, [])
    
    const permissionsState = useMemo<PermissionsState>(() => ({
        voicePermissionsGranted,
        checkVoicePermissions,
        locationPermissionsGranted,
        checkLocationPermissions,
        mediaPermissionsGranted,
        checkMediaPermissions,
        notificationPermissionsGranted,
        permissionsLoading,
    }), [voicePermissionsGranted, locationPermissionsGranted, mediaPermissionsGranted, notificationPermissionsGranted, permissionsLoading])

    return (
        <PermissionsContext.Provider value={permissionsState}>
            {children}
        </PermissionsContext.Provider>
    )
}

export const usePermissions = () => {
    const context = useContext<PermissionsState>(PermissionsContext)
    if (context === null) {
        throw new Error("usePermissions must be used within a PermissionsContextProvider")
    }
    return context
}