import React, { useEffect, useState } from 'react';
import { DevSettings, I18nManager, LogBox, Platform, useColorScheme } from 'react-native';

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import data from '@emoji-mart/data';
import notifee, { EventType } from '@notifee/react-native';
import Geolocation from '@react-native-community/geolocation';
import { getMessaging } from '@react-native-firebase/messaging';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { init, SearchIndex } from 'emoji-mart';
import type { LocalMessage, StreamChat, TextComposerMiddleware } from 'stream-chat';
import {
  Chat,
  createTextComposerEmojiMiddleware,
  LiveLocationManagerProvider,
  OverlayProvider,
  setupCommandUIMiddlewares,
  SqliteClient,
  Streami18n,
  ThemeProvider,
  useOverlayContext,
  WithComponents,
} from 'stream-chat-react-native';

import { MenuDrawer } from './src/components/MenuDrawer';
import { OfflineDbBoundary } from './src/components/OfflineDbBoundary';
import { useSampleAppComponentOverrides } from './src/components/SampleAppComponentOverrides';
import {
  MessageInputFloatingConfigItem,
  MessageOverlayBackdropConfigItem,
  MessageListImplementationConfigItem,
  MessageListModeConfigItem,
  MessageListPruningConfigItem,
} from './src/components/SecretMenu.tsx';
import { AppContext } from './src/context/AppContext';
import { StreamChatProvider } from './src/context/StreamChatContext';
import { UserSearchProvider } from './src/context/UserSearchContext';
import { useChatClient } from './src/hooks/useChatClient';
import { useStreamChatTheme } from './src/hooks/useStreamChatTheme';
import { AdvancedUserSelectorScreen } from './src/screens/AdvancedUserSelectorScreen';
import { ChannelDetailsScreen } from './src/screens/ChannelDetailsScreen.tsx';
import { ChannelFilesScreen } from './src/screens/ChannelFilesScreen';
import { ChannelImagesScreen } from './src/screens/ChannelImagesScreen';
import { ChannelPinnedMessagesScreen } from './src/screens/ChannelPinnedMessagesScreen';
import { ChannelScreen } from './src/screens/ChannelScreen';
import { ChatScreen } from './src/screens/ChatScreen';
import { LoadingScreen } from './src/screens/LoadingScreen';
import { MapScreen } from './src/screens/MapScreen';
import { NewDirectMessagingScreen } from './src/screens/NewDirectMessagingScreen';
import { NewGroupChannelAddMemberScreen } from './src/screens/NewGroupChannelAddMemberScreen';
import { NewGroupChannelAssignNameScreen } from './src/screens/NewGroupChannelAssignNameScreen';
import { ThreadScreen } from './src/screens/ThreadScreen';
import { UserSelectorScreen } from './src/screens/UserSelectorScreen';

import type { StackNavigatorParamList, UserSelectorParamList } from './src/types';

import AsyncStore from './src/utils/AsyncStore.ts';
import { navigateToChannel, RootNavigationRef } from './src/utils/RootNavigation';
import { watchLocation } from './src/utils/watchLocation';

Geolocation.setRNConfiguration({
  skipPermissionRequests: false,
  authorizationLevel: 'always',
  locationProvider: 'playServices',
});

init({ data });

if (__DEV__) {
  DevSettings.addMenuItem('Reset local DB (offline storage)', () => {
    SqliteClient.resetDB();
    console.info('Local DB reset');
  });
}

LogBox.ignoreLogs(['Non-serializable values were found in the navigation state']);
console.assert = () => null;

// when a channel id is set here, the intial route is the channel screen
const initialChannelIdGlobalRef = { current: '' };

notifee.onBackgroundEvent(async ({ detail, type }) => {
  // user press on notification detected while app was on background on Android
  if (type === EventType.PRESS) {
    const channelId = detail.notification?.data?.channel_id as string;
    if (channelId) {
      navigateToChannel(channelId);
    }
    await Promise.resolve();
  }
});

const Drawer = createDrawerNavigator();
const Stack = createNativeStackNavigator<StackNavigatorParamList>();
const UserSelectorStack = createNativeStackNavigator<UserSelectorParamList>();
const RTL_STORAGE_KEY = '@stream-rn-sampleapp-rtl-enabled';
const accessibilityConfig = { enabled: true };

const App = () => {
  const { chatClient, isConnecting, loginUser, logout, switchUser } = useChatClient();
  const [rtlEnabled, setRtlEnabled] = useState<boolean | undefined>(undefined);
  const [messageListImplementation, setMessageListImplementation] = useState<
    MessageListImplementationConfigItem['id'] | undefined
  >(undefined);
  const [messageListMode, setMessageListMode] = useState<
    MessageListModeConfigItem['mode'] | undefined
  >(undefined);
  const [messageListPruning, setMessageListPruning] = useState<
    MessageListPruningConfigItem['value'] | undefined
  >(undefined);
  const [messageInputFloating, setMessageInputFloating] = useState<
    MessageInputFloatingConfigItem['value'] | undefined
  >(undefined);
  const [messageOverlayBackdrop, setMessageOverlayBackdrop] = useState<
    MessageOverlayBackdropConfigItem['value'] | undefined
  >(undefined);
  const colorScheme = useColorScheme();
  const streamChatTheme = useStreamChatTheme();
  const streami18n = new Streami18n();
  const componentOverrides = useSampleAppComponentOverrides(messageOverlayBackdrop);

  const setRTLEnabled = React.useCallback(async (enabled: boolean) => {
    await AsyncStore.setItem(RTL_STORAGE_KEY, enabled);
    I18nManager.allowRTL(enabled);
    I18nManager.forceRTL(enabled);
    I18nManager.swapLeftAndRightInRTL(enabled);
    setRtlEnabled(enabled);
    DevSettings.reload();
  }, []);

  useEffect(() => {
    const messaging = getMessaging();
    const unsubscribeOnNotificationOpen = messaging.onNotificationOpenedApp((remoteMessage) => {
      // Notification caused app to open from background state on iOS
      const channelId = remoteMessage.data?.channel_id as string;
      if (channelId) {
        navigateToChannel(channelId);
      }
    });
    // handle notification clicks on foreground
    const unsubscribeForegroundEvent = notifee.onForegroundEvent(({ detail, type }) => {
      if (type === EventType.PRESS) {
        // user has pressed the foreground notification
        const channelId = detail.notification?.data?.channel_id as string;
        if (channelId) {
          navigateToChannel(channelId);
        }
      }
    });
    notifee.getInitialNotification().then((initialNotification) => {
      if (initialNotification) {
        // Notification caused app to open from quit state on Android
        const channelId = initialNotification.notification.data?.channel_id as string;
        if (channelId) {
          initialChannelIdGlobalRef.current = channelId;
        }
      }
    });
    messaging.getInitialNotification().then((remoteMessage) => {
      if (remoteMessage) {
        // Notification caused app to open from quit state on iOS
        const channelId = remoteMessage.data?.channel_id as string;
        if (channelId) {
          // this will make the app to start with the channel screen with this channel id
          initialChannelIdGlobalRef.current = channelId;
        }
      }
    });
    const getAppConfig = async () => {
      const storedRTLEnabled = await AsyncStore.getItem<boolean>(RTL_STORAGE_KEY, false);
      const nextRTLEnabled = !!storedRTLEnabled;

      I18nManager.allowRTL(nextRTLEnabled);
      I18nManager.forceRTL(nextRTLEnabled);
      I18nManager.swapLeftAndRightInRTL(nextRTLEnabled);

      if (I18nManager.isRTL !== nextRTLEnabled) {
        DevSettings.reload();
        return;
      }

      setRtlEnabled(nextRTLEnabled);

      const messageListImplementationStoredValue = await AsyncStore.getItem(
        '@stream-rn-sampleapp-messagelist-implementation',
        { id: 'flatlist' },
      );
      const messageListModeStoredValue = await AsyncStore.getItem(
        '@stream-rn-sampleapp-messagelist-mode',
        { mode: 'default' },
      );
      const messageListPruningStoredValue = await AsyncStore.getItem(
        '@stream-rn-sampleapp-messagelist-pruning',
        { value: undefined },
      );
      const messageInputFloatingStoredValue = await AsyncStore.getItem(
        '@stream-rn-sampleapp-messageinput-floating',
        { value: false },
      );
      const messageOverlayBackdropStoredValue = await AsyncStore.getItem(
        '@stream-rn-sampleapp-message-overlay-backdrop',
        { value: 'default' },
      );
      setMessageListImplementation(
        messageListImplementationStoredValue?.id as MessageListImplementationConfigItem['id'],
      );
      setMessageListMode(messageListModeStoredValue?.mode as MessageListModeConfigItem['mode']);
      setMessageListPruning(
        messageListPruningStoredValue?.value as MessageListPruningConfigItem['value'],
      );
      setMessageInputFloating(
        messageInputFloatingStoredValue?.value as MessageInputFloatingConfigItem['value'],
      );
      setMessageOverlayBackdrop(
        messageOverlayBackdropStoredValue?.value as MessageOverlayBackdropConfigItem['value'],
      );
    };
    getAppConfig();
    return () => {
      unsubscribeOnNotificationOpen();
      unsubscribeForegroundEvent();
    };
  }, []);

  useEffect(() => {
    if (!chatClient) {
      return;
    }
    chatClient.setMessageComposerSetupFunction(({ composer }) => {
      composer.updateConfig({
        drafts: {
          enabled: true,
        },
        linkPreviews: {
          enabled: true,
        },
      });

      setupCommandUIMiddlewares(composer);

      composer.textComposer.middlewareExecutor.insert({
        middleware: [
          createTextComposerEmojiMiddleware({
            emojiSearchIndex: SearchIndex,
          }) as TextComposerMiddleware,
        ],
        position: { after: 'stream-io/text-composer/mentions-middleware' },
        unique: true,
      });
    });
  }, [chatClient]);

  if (rtlEnabled === undefined || !messageListImplementation || !messageListMode) {
    return;
  }

  return (
    <SafeAreaProvider
      style={{
        backgroundColor: streamChatTheme.colors?.white_snow || '#FCFCFC',
      }}
    >
      <GestureHandlerRootView style={{ flex: 1 }}>
        <WithComponents overrides={componentOverrides}>
          <OverlayProvider
            accessibility={accessibilityConfig}
            value={{ style: streamChatTheme }}
            i18nInstance={streami18n}
          >
            <ThemeProvider style={streamChatTheme}>
              <NavigationContainer
                ref={RootNavigationRef}
                theme={{
                  colors: {
                    ...(colorScheme === 'dark' ? DarkTheme : DefaultTheme).colors,
                    background: streamChatTheme.colors?.white_snow || '#FCFCFC',
                  },
                  fonts: (colorScheme === 'dark' ? DarkTheme : DefaultTheme).fonts,
                  dark: colorScheme === 'dark',
                }}
              >
                <AppContext.Provider
                  value={{
                    chatClient,
                    loginUser,
                    logout,
                    switchUser,
                    rtlEnabled,
                    setRTLEnabled,
                    messageListImplementation,
                    messageInputFloating: messageInputFloating ?? false,
                    messageListMode,
                    messageListPruning,
                  }}
                >
                  {isConnecting && !chatClient ? (
                    <LoadingScreen />
                  ) : chatClient ? (
                    <DrawerNavigatorWrapper chatClient={chatClient} i18nInstance={streami18n} />
                  ) : (
                    <UserSelector />
                  )}
                </AppContext.Provider>
              </NavigationContainer>
            </ThemeProvider>
          </OverlayProvider>
        </WithComponents>
      </GestureHandlerRootView>
    </SafeAreaProvider>
  );
};

const DrawerNavigator: React.FC = () => (
  <LiveLocationManagerProvider watchLocation={watchLocation}>
    <Drawer.Navigator
      drawerContent={MenuDrawer}
      screenOptions={{
        drawerStyle: {
          width: 300,
        },
      }}
    >
      <Drawer.Screen component={HomeScreen} name='HomeScreen' options={{ headerShown: false }} />
    </Drawer.Navigator>
  </LiveLocationManagerProvider>
);

const isMessageAIGenerated = (message: LocalMessage) => !!message.ai_generated;

const DrawerNavigatorWrapper: React.FC<{
  chatClient: StreamChat;
  i18nInstance: Streami18n;
}> = ({ chatClient, i18nInstance }) => {
  // `attempt` re-mounts <Chat> after the offline database has been deleted;
  // `offlineSupport` is switched off once there is no usable encryption key.
  const [attempt, setAttempt] = useState(0);
  const [offlineSupport, setOfflineSupport] = useState(true);

  // The boundary stops rendering its children once it has caught (see its render), and
  // nothing else clears that. Keying it on both recovery levers re-mounts it when one is
  // pulled - without that it would sit on a blank screen forever, having already deleted
  // the database.
  return (
    <OfflineDbBoundary
      key={`${attempt}-${offlineSupport}`}
      onGiveUp={() => setOfflineSupport(false)}
      onRetry={() => setAttempt((value) => value + 1)}
    >
      <Chat
        client={chatClient}
        enableOfflineSupport={offlineSupport}
        i18nInstance={i18nInstance}
        isMessageAIGenerated={isMessageAIGenerated}
        useNativeMultipartUpload
      >
        <StreamChatProvider>
          <UserSearchProvider>
            <DrawerNavigator />
          </UserSearchProvider>
        </StreamChatProvider>
      </Chat>
    </OfflineDbBoundary>
  );
};

const UserSelector = () => (
  <UserSelectorStack.Navigator initialRouteName='UserSelectorScreen'>
    <UserSelectorStack.Screen
      component={AdvancedUserSelectorScreen}
      name='AdvancedUserSelectorScreen'
      options={{ gestureEnabled: false }}
    />
    <UserSelectorStack.Screen
      component={UserSelectorScreen}
      name='UserSelectorScreen'
      options={{ gestureEnabled: false, headerShown: false }}
    />
  </UserSelectorStack.Navigator>
);

// TODO: Split the stack into multiple stacks - ChannelStack, CreateChannelStack etc.
const HomeScreen = () => {
  const { overlay } = useOverlayContext();

  return (
    <Stack.Navigator
      initialRouteName={initialChannelIdGlobalRef.current ? 'ChannelScreen' : 'MessagingScreen'}
    >
      <Stack.Screen
        component={ChatScreen}
        name='MessagingScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ChannelScreen}
        initialParams={
          initialChannelIdGlobalRef.current
            ? { channelId: initialChannelIdGlobalRef.current }
            : undefined
        }
        name='ChannelScreen'
        options={{
          gestureEnabled: Platform.OS === 'ios' && overlay === 'none',
          headerShown: false,
        }}
      />
      <Stack.Screen
        name='MapScreen'
        component={MapScreen}
        options={{ headerTitle: 'Location', headerBackTitle: 'Back' }}
      />
      <Stack.Screen
        component={NewDirectMessagingScreen}
        name='NewDirectMessagingScreen'
        options={{
          headerShown: false,
        }}
      />
      <Stack.Screen
        component={NewGroupChannelAddMemberScreen}
        name='NewGroupChannelAddMemberScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={NewGroupChannelAssignNameScreen}
        name='NewGroupChannelAssignNameScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ChannelDetailsScreen}
        name='ChannelDetailsScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ChannelImagesScreen}
        name='ChannelImagesScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ChannelFilesScreen}
        name='ChannelFilesScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ChannelPinnedMessagesScreen}
        name='ChannelPinnedMessagesScreen'
        options={{ headerShown: false }}
      />
      <Stack.Screen
        component={ThreadScreen}
        name='ThreadScreen'
        options={{
          gestureEnabled: Platform.OS === 'ios' && overlay === 'none',
          headerShown: false,
        }}
      />
    </Stack.Navigator>
  );
};

export default App;