import * as Amplitude from 'expo-analytics-amplitude'
import Constants from 'expo-constants'
import { isDevice } from 'expo-device'
import * as Localization from 'expo-localization'
import { PermissionStatus } from 'expo-modules-core'
import * as Notifications from 'expo-notifications'
import * as TaskManager from 'expo-task-manager'
import { useEffect, useState } from 'react'
import { Platform } from 'react-native'
import * as Sentry from 'sentry-expo'

import { postWithFreshToken } from '../components/Network'
import { User } from '../user'

/** updates the backend with new tokens */
export const usePushNotificationToken = (): string | null => {
  const [token, setToken] = useState<string | null>(null)
  getPushNotificationToken()
    .then(setToken)
    .catch((error) => {
      if (isDevice) Sentry.Native.captureException(error)
    })

  return token
}

const getNotificationsPermissionStatus = async () => {
  const { status } = await Notifications.getPermissionsAsync()
  return status
}

const askForNotificationsPermission = async () => {
  const { status } = await Notifications.requestPermissionsAsync()
  return status
}

/**
 * Check if the user allowed notifications.
 * Asks permission for superusers on real devices if undetermined.
 */
const determineNotificationsAllowed = async (): Promise<boolean> => {
  if (!Constants.isDevice) return false

  let status = await getNotificationsPermissionStatus()

  status =
    status === PermissionStatus.UNDETERMINED
      ? await askForNotificationsPermission()
      : status

  return status === PermissionStatus.GRANTED
}

export async function getPushNotificationToken(): Promise<string> {
  const token = (await Notifications.getExpoPushTokenAsync())?.data

  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.DEFAULT,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    })
  }

  return token
}

/**
 * Log in amplitude when a notification is received while the app is foregrounded
 */
export function useLogNotificationReceived(): void {
  Notifications.setNotificationHandler({
    handleNotification: async (notification) => {
      Amplitude.logEventWithPropertiesAsync('notification_received', {
        inapp: true,
        ...notification,
      })

      return {
        shouldShowAlert: true,
        shouldPlaySound: false,
        shouldSetBadge: false,
      }
    },
  })
}

// The task must be defined in the global scope, see
// https://docs.expo.dev/versions/latest/sdk/notifications/#handling-incoming-notifications-when-the-app-is-1
const LOG_NOTIFICATION_RECEIVED_NAME = 'LOG_NOTIFICATION_RECEIVED'
TaskManager.defineTask(LOG_NOTIFICATION_RECEIVED_NAME, ({ data, error }) => {
  if (error) {
    Sentry.Native.captureMessage(error.message, Sentry.Native.Severity.Warning)
    return
  }
  if (data) {
    Amplitude.logEventWithPropertiesAsync('notification_received', {
      inapp: false,
      ...data,
    })
  }
})

export function useLogNotificationReceivedInBackground(): void {
  useEffect(() => {
    // Unfortunately this isn’t possible, due to some App Store review-related things.
    // See https://github.com/expo/expo/issues/14715
    if (Constants.appOwnership === 'expo') return

    Notifications.registerTaskAsync(LOG_NOTIFICATION_RECEIVED_NAME)

    return () => {
      Notifications.unregisterTaskAsync(LOG_NOTIFICATION_RECEIVED_NAME)
    }
  }, [])
}

/**
 * Check if the user is allowed to use notifications (superuser-only)
 * Ask for notifications permissions if they have not been decided
 * Inform the backend of the latest device notifications permissions
 */
export function useNotificationPermissions(user: User): void {
  const isSuperUser = user?.permissions?.includes('superuser')
  const [allowed, setAllowed] = useState<boolean | null>(null)

  // Currently "feature-flagged" for superusers
  if (isSuperUser) {
    determineNotificationsAllowed().then(setAllowed)
  }

  const token = usePushNotificationToken()
  useEffect(() => {
    if (!token) return
    if (!isSuperUser) return
    if (allowed === null) return

    const body = JSON.stringify({
      token,
      deviceType: Platform.OS,
      notificationsEnabled: allowed,
      locale: Localization.locale,
      timezone: Localization.timezone,
    })
    const options = { headers: { 'content-type': 'application/json' } }
    postWithFreshToken(`api/notifications`, body, user, options)
  }, [allowed, isSuperUser, token, user])
}
