import { FontAwesome } from "@expo/vector-icons";
import { RouteProp } from "@react-navigation/native";
import { booleanPointInPolygon, circle } from '@turf/turf';
import * as Notifications from 'expo-notifications';
import { observer } from "mobx-react";
import React, { useEffect } from "react";
import { Alert, AppState, Image, Keyboard, Platform, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import MapView, { LatLng, Marker } from "react-native-maps";
import { Chip } from "react-native-paper";
import { PERMISSIONS } from "react-native-permissions";
import Spokestack from 'react-native-spokestack';
import { Landmark, useLandmarks } from "../../../hooks/useLandmarks";
import { useProfile } from "../../../hooks/useProfile";
import { AuthTabskParamList as AuthTabsParamList, AuthTabsNavigationProp } from "../../../navigation/AuthorizedNavigator";
import { authStore } from "../../../stores/AuthStore";
import { NotifType } from "../../../types";
import { checkGivenPermissions, colors, getLocationPermissions, lmTypes } from "../../../utils/GlobalUtils";
import Badge from "../../Badge";
import { IconButton } from "../../Buttons";
import AddLandmarkPanel from "../Panels/AddLandmarkPanel";
import AlertedLandmarkPanel from "../Panels/AlertedLandmarkPanel";
import { FilterPanel } from "../Panels/FilterPanel/FilterPanel";
import LandmarkDetails from "../Panels/LandmarkDetailsPanel/LandmarkDetails";
import { VoicePanel } from "../Panels/VoicePanel";
import mapStyles from "./Map.styles";
import { useMapState } from "./useMapState";

/**
 * An interface representing the user location retrieved from [expo-location]{@link https://docs.expo.dev/versions/latest/sdk/location/}.
 * @category Map
 */
export interface UserLocation {
    latitude: number;
    longitude: number;
    heading?: number;
}

type AuthTabsMapRouteProp = RouteProp<AuthTabsParamList, 'Map'>;


/**
 * The screen component containing the Map and all related functionality. Uses [react-native-maps]{@link https://github.com/react-native-maps/react-native-maps}
 * @category Map
 * @component
 */
const Map: React.FC<{navigation: AuthTabsNavigationProp, route: AuthTabsMapRouteProp}> = ({navigation, route}) => {
    // get all state from custom hook
    const mapState = useMapState()
    // bring in all landmarks from useLandmark hook
    const { landmarks, refetchLandmarks } = useLandmarks({});
    const { profile, toggleTipsAsync } = useProfile(authStore.userId)

    /**
     * If the user has their preferences configured to show tips, show them on page load. Recheck every time show_tips changes
     */
    useEffect(() => {
        const showTip = () =>  {
            if (profile?.show_tips) {
                console.log('User has tips configured, showing tips.')
                Alert.alert(
                    'Welcome!', 
                    "There are 3 ways to add a landmark to the map: \n\n - Press and hold where you'd like to add it on the map \n\n - Press the add button to add a landmark at your current location \n\n - Press the mic button and use voice commands.", 
                    [{text: "Don't show this again", onPress: async () => await toggleTipsAsync()}, {text: 'Ok'}]
                )   
            }
            else {
                console.log('User does not have tips configured')
            }
        }
        showTip();
    }, [profile?.show_tips])

    /**
     * If the ReactNavigation route prop changes, check if it contains incoming selected landmarks, display them if there are. This will be triggered by incoming notifcations 
     * (See the AuthorizedNavigator page for the useEffect that will trigger this)
     */
    useEffect(() => {
        if (route?.params?.selectedLandmark) {
            mapState.setSelectedLandmarkId(route?.params?.selectedLandmark)
        }
        if (route?.params?.selectedLandmarks) {
            mapState.toggleAlertedLmPanel(true)
        }
    }, [route])

    /**
     * Toggle the lm details panel when a new selected landmark is detected (triggered by pressing on a map marker, or from the list of nearby landmarks)
     */
    useEffect(() => {
        console.log("id: " + mapState.selectedLandmarkId)
        if (mapState.selectedLandmarkId) {
            

            const landmark = landmarks.find(lm => lm.id == mapState.selectedLandmarkId)
            mapState.mapRef.current.animateToRegion({latitude: landmark.latitude, longitude: landmark.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01})
            mapState.toggleLmDetails(true)
        }
    }, [mapState.selectedLandmarkId])

    /**
     * Clear selected landmark when landmark details panel is closed
     */
     useEffect(() => {
        if (!mapState.lmDetailsVisible) {
            mapState.setSelectedLandmarkId('')
        }
    }, [mapState.lmDetailsVisible])
    
    /**
     * Gets speech permissions from user, runs every time app is brought to foreground
     */
    useEffect(() => {
        const getSpeechPermissions = async () => {
            if (AppState.currentState == 'active') {
                await getLocationPermissions()
                console.log('Checking voice permissions...')
                if (Platform.OS == 'android') {        
                    const permitted = await checkGivenPermissions([PERMISSIONS.ANDROID.RECORD_AUDIO])
                    mapState.toggleVoicePermission(permitted)
                    console.log('Voice permission granted: ' + permitted)
                }
                else if (Platform.OS == 'ios') {
                    const permitted = await checkGivenPermissions([PERMISSIONS.IOS.SPEECH_RECOGNITION, PERMISSIONS.IOS.MICROPHONE])
                    mapState.toggleVoicePermission(permitted)
                    console.log('Voice permission granted: ' + permitted)
                }
            }   
        }
        getSpeechPermissions()
      }, [AppState.currentState])

    /**
     * Gets foreground location permissions from user, runs every time app is brought to foreground
     */
    useEffect(() => {
        const checkForegroundLocationPermissions = async () =>  {
            if (AppState.currentState == 'active') {
                console.log('Checking location permissions...')
                if (Platform.OS == 'android') {        
                    const permitted = await checkGivenPermissions([PERMISSIONS.ANDROID.ACCESS_COARSE_LOCATION, PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION])
                    mapState.toggleFgroundLocationPermission(permitted)
                    console.log('Location permission granted: ' + permitted)
                }
                else if (Platform.OS == 'ios') {
                    const permitted = await checkGivenPermissions([PERMISSIONS.IOS.LOCATION_WHEN_IN_USE])
                    mapState.toggleFgroundLocationPermission(permitted)
                    console.log('Location permission granted: ' + permitted)
                }
            }
        }
        checkForegroundLocationPermissions();
    }, [AppState.currentState])

    /**
     * Gets background location permissions from user, runs every time app is brought to foreground
     */
    useEffect(() => {
        const checkBackgroundLocationPermissions = async () =>  {
            if (AppState.currentState == 'active') {
                if (Platform.OS == 'android') {        
                    const permitted = await checkGivenPermissions([PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION])
                    mapState.toggleBgroundLocationPermission(permitted)
                    console.log('Background location permission granted: ' + permitted)
                }
                else if (Platform.OS == 'ios') {
                    const permitted = await checkGivenPermissions([PERMISSIONS.IOS.LOCATION_ALWAYS])
                    mapState.toggleBgroundLocationPermission(permitted)
                    console.log('Background location permission granted: ' + permitted)
                }
            }
        }
        checkBackgroundLocationPermissions();
    }, [AppState.currentState])

    /**
     * Gets net location permission the existing location permission states. It will check foreground and background permissions and AppState, 
     * then from that it will decide if location-enabled features should be activated (through the mapState state values).
     */
    useEffect(() => {
        const updateLocationPermissionOnAppStateChange = async () => {
            const netLocationPermissions = mapState.bgroundLocationPermission || (mapState.fgroundLocationPermission && AppState.currentState == 'active')
            console.log('Appstate, or location permissions changed, net location permissions found to be: ' + netLocationPermissions)
            mapState.toggleLocationPermitted(netLocationPermissions)
        }
        updateLocationPermissionOnAppStateChange()
    }, [AppState.currentState, mapState.bgroundLocationPermission, mapState.fgroundLocationPermission]) 

    useEffect(() => {
        const refetchLandmarksOnFilterOptionsChange = async () => {
            applyFilters(landmarks)
            console.log('filters changed')
        }
        refetchLandmarksOnFilterOptionsChange()
    }, [mapState.lmFilteredTypes, mapState.onlyOwned, mapState.minLmRating])

    const applyFilters = (landmarks: Landmark[]): Landmark[] => {
        if (landmarks?.length > 0) {
            if (mapState.minLmRating > 0) {
                landmarks = landmarks?.filter(lm => lm.rating >= mapState.minLmRating);
            }
        
            if (mapState.lmFilteredTypes?.length > 0) {
                if (mapState.lmFilteredTypes.length > 0) {
                    landmarks = landmarks?.filter(lm => mapState.lmFilteredTypes.includes(lm.landmark_type));   
                }
            }
        
            if (mapState.onlyOwned) {
                landmarks = landmarks?.filter(lm => lm.user == authStore.userId);
            }
        }
    
        return landmarks
    }

    /**
     * Triggered by long pressing on the map. 
     * Sets {@linkcode newLandmarkState} to a skeleton {@link Landmark} object that only contains the pressed coordinates.
     * Triggers {@link openAddLandmark} via useEffect because the asyncronous nature of useState does not set the coordinates fast enough to toggle the
     * modal directly through this method (this issue shows up in many other parts of the app where a modal is toggled by a boolean, and is solved in the same way).
     */
     const promptAddLandmark = (coordinate: LatLng) => {
        console.log('Opening add landmark panel...')
        mapState.setNewLandmark({latitude: coordinate.latitude, longitude: coordinate.longitude});
        mapState.toggleLmAdd(true)
        mapState.toggleLmDetails(false)   
    }

    /**
     * Triggered by on a {@link Landmark} displayed on the map. 
     * Sets {@linkcode selectedLandmark} to the pressed {@link Landmark} object's value and toggles the {@link LandmarkDetails} modal.
     */
    const focusLandmark = (landmark: Landmark) => {
        console.log('Focusing landmark: ' + landmark.id + '.')
        mapState.mapRef.current?.animateToRegion({latitude: landmark.latitude, longitude: landmark.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01})
        if (mapState.selectedLandmarkId == landmark.id) {
            mapState.toggleLmDetails(true)
        }
        else {
            mapState.setSelectedLandmarkId(landmark.id);
        }
    }

    /**
     * Animates the map to fly over to and focus on the user's location.
     */
    const flyToUser = () => {
        console.log('Centering on user.')
        if (mapState.userLocation) {
            mapState.mapRef.current?.animateToRegion({latitude: mapState.userLocation.latitude, longitude: mapState.userLocation.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01})
        }
    }

    /**
     * Activates speech recognition and opens the voice panel
     */
    const startSpeech = () => {
        mapState.toggleLmDetails(false);
        mapState.toggleLmAdd(false);
        Spokestack.activate()
    }

    /**
     * Gets initial region that map should zoom into from current user location
     */
    const getInitialRegion = () => {
        if (mapState.userLocation) {
            return {latitude: mapState.userLocation.latitude, longitude: mapState.userLocation.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01}
        }
        return undefined
    }

    useEffect(() => {
        
    }, [mapState.landmarksNearUser])

    /**
     * Method that runs every time user location changes, updates user location state in memory and checks if any landmarks are nearby
     */
    const updateLocation = async (coord: LatLng) => {
        mapState.setUserLocation(coord)
        // get 10m radius around user
        const userAlertRadius = circle([coord.longitude, coord.latitude], 10, {units: 'meters'})

        // check each landmark to see if its inside user radius. if it is, and it isn't already in the list of notified landmarks, add it
        const newLandmarksNearUser = landmarks?.filter(lm => {
            const landmarkNearUser = booleanPointInPolygon([lm.longitude, lm.latitude], userAlertRadius)
            return landmarkNearUser
        })

        // to prevent duplicate notifications make a list of landmarks that weren't previously near the user. 
        // these are the only ones that the user will be notified of
        const newLandmarksNotPreviouslyNearUser = newLandmarksNearUser.filter(lm => mapState.landmarksNearUser.some(origLm => lm == origLm.id))

        // update list
        mapState.setLandmarksNearUser(newLandmarksNearUser)

        // if there are any new landmarks near user, create a notification for them and send it
        if (newLandmarksNotPreviouslyNearUser?.length > 0) {
            const body = newLandmarksNotPreviouslyNearUser.length > 1 ? "There are new landmarks near by. Tap here to view" : "There is a new landmark close by. Tap here to view"
            const notifType: NotifType = newLandmarksNotPreviouslyNearUser.length > 1 ? 'near-landmarks' : 'near-landmark'
            const data = {notif_type: notifType, landmarks: newLandmarksNotPreviouslyNearUser.length == 1 ? newLandmarksNearUser : null}
            await Notifications.scheduleNotificationAsync({
                content: {
                  title: "⚠ Landmarks close by ⚠",
                  body: body,
                  data: data
                },
                trigger: { seconds: 2 },
              });
        }
    }

    const focusNearbyLandmarks = () => {
        if (mapState.landmarksNearUser?.length > 1) {
            mapState.toggleAlertedLmPanel(true)
        }
        else if (mapState.landmarksNearUser?.length === 1) { 
            mapState.setSelectedLandmarkId(mapState.landmarksNearUser[0].id)
        }
    }

    return (
        <TouchableWithoutFeedback>
            <>
            {/*Main map component*/}
            <MapView 
                onPress={() => Keyboard.dismiss()}
                testID="mapView"
                ref={mapState.mapRef} 
                style={{width: '100%', height: '100%'}}
                initialRegion={getInitialRegion()} 
                onLongPress={(e) => promptAddLandmark(e.nativeEvent.coordinate)} 
                showsUserLocation={mapState.locationPermitted} 
                onUserLocationChange={e => updateLocation(e.nativeEvent.coordinate)}
                followsUserLocation={mapState.followUser}
                showsMyLocationButton={false}>
            {applyFilters(landmarks)?.map((landmark) => {
                let trackChanges = false;
                if (landmark?.id == mapState.selectedLandmarkId) {
                    trackChanges = true;
                }
                return (
                    <Marker 
                        tracksViewChanges={trackChanges}
                        onPress={() => focusLandmark(landmark)}
                        key={landmark.id} 
                        coordinate={{latitude: landmark.latitude as number, longitude: landmark.longitude as number}} >
                        { landmark.landmark_type ? <Image style={{height: 35, width: 25}} source={lmTypes[landmark.landmark_type].image} /> : null}
                    </Marker>)})}
            </MapView>
             
            {/*Filter chips and button*/}
            {!mapState.filterVisible ? 
            <View style={{top: 10, marginLeft: 40, marginRight: 20, position: 'absolute', flexDirection: "row-reverse", justifyContent: 'flex-end'}}>
                <IconButton size={20} color={colors.red} style={[mapStyles.filterButton]} icon="filter" onPress={() => mapState.toggleFilter(true)}/>
                <ScrollView horizontal={true} contentContainerStyle={{alignItems: 'center'}} style={{marginHorizontal: 10, flexDirection: 'row'}}>
                {mapState.onlyOwned ? <Chip avatar={(<FontAwesome name="user" size={20} color='gray' style={{textAlign: 'center', textAlignVertical: 'center'}} />)} style={{borderWidth: 1, borderColor: 'lightgray', marginRight: 5, marginLeft: 10}} onClose={() => mapState.toggleOnlyOwned(false)}>My landmarks</Chip> : null}
                {mapState.lmFilteredTypes.map((type, i) => {
                    return (
                        <Chip key={i} avatar={(<Image style={{height: 22, width: 17}} source={lmTypes[type].image} />)} style={{borderWidth: 1, borderColor: 'lightgray', marginHorizontal: 5, justifyContent: 'center'}} onClose={() => mapState.setLmTypeFilter(mapState.lmFilteredTypes.filter(t => t !== type))}>{lmTypes[type].label}</Chip>
                    )
                })}
                {mapState.minLmRating > 0 ? <Chip avatar={(<FontAwesome name="star" size={20} color='gray' style={{textAlign: 'center', textAlignVertical: 'center'}} />)} style={{borderWidth: 1, borderColor: 'lightgray', marginLeft: 5, marginRight: 10}} onClose={() => mapState.setMinLmRating(0)}>Minimum rating: {mapState.minLmRating}</Chip> : null}
                </ScrollView>
            </View> : null}
            
            
            {/*Map buttons*/}
            {mapState.landmarksNearUser?.length > 0 ? 
            <TouchableOpacity style={[mapStyles.lowerMapButton, mapStyles.alertButton]} onPress={focusNearbyLandmarks}>
                <FontAwesome name='exclamation-triangle' size={20} color='white' />
                <Badge positioning={{bottom: 7, right: 4}} value={mapState.landmarksNearUser.length}/>
            </TouchableOpacity> : null}
            <IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.userLocationButton]} icon="location-arrow" onPress={flyToUser}/>
            <IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.addLandmarkButton]} icon="plus" onPress={() => promptAddLandmark(mapState.userLocation)}/>
            {mapState.locationPermitted && mapState.voicePermission ? 
            <IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.voiceButton]} icon="microphone" onPress={startSpeech}/>: null}

            {/*Map Panels*/}
            {mapState.voicePermission && mapState.locationPermitted ? <VoicePanel  
                toggleAlertedLandmarksVisible={mapState.toggleAlertedLmPanel}
                navigation={navigation}
                userCoords={{longitude: mapState.userLocation?.longitude, latitude: mapState.userLocation?.latitude}}
                toggleVoiceVisible={mapState.toggleVoiceVisible} 
                toggleLmDetails={mapState.toggleLmDetails}
                setSelectedLandmarkId={mapState.setSelectedLandmarkId}
                voiceVisible={mapState.voiceVisible}
                newLandmark={mapState.newLandmark} 
                setNewLandmark={mapState.setNewLandmark}
                /> : null }
            <AddLandmarkPanel 
                setNewLandmark={mapState.setNewLandmark} 
                setVisible={mapState.toggleLmAdd} 
                newLandmark={mapState.newLandmark}
                visible={mapState.lmAddVisible} />
            <LandmarkDetails 
                visible={mapState.lmDetailsVisible}
                toggleLmDetails={mapState.toggleLmDetails}
                setLandmark={mapState.setSelectedLandmarkId} 
                toggleDetailsPanel={mapState.toggleLmDetails} 
                setEditing={mapState.toggleLmDetailsEditing} 
                editingEnabled={mapState.lmDetailsEditing} 
                landmarkId={mapState.selectedLandmarkId} />
            <FilterPanel 
                visible={mapState.filterVisible}
                toggleOnlyOwned={mapState.toggleOnlyOwned} 
                onlyOwned={mapState.onlyOwned} toggleFilter={mapState.toggleFilter} 
                setMinLmRating={mapState.setMinLmRating} 
                setLmFilteredTypes={mapState.setLmTypeFilter} 
                lmFilteredTypes={mapState.lmFilteredTypes} 
                minLmRating={mapState.minLmRating} />
            <AlertedLandmarkPanel 
                focusLandmark={focusLandmark}
                alertedLmPanelVisible={mapState.alertedLmVisible}
                toggleAlertedLmPanel={mapState.toggleAlertedLmPanel}
                alertedLandmarks={mapState.landmarksNearUser}/>
            </>
        </TouchableWithoutFeedback> )
}

export default observer(Map);