import React, { useCallback, useEffect, useReducer, useState } from 'react';
import { io } from 'socket.io-client';
import { ThemedText, ThemedView } from '@/components/theme/Theme';
import { Alert, Linking, Platform, StyleSheet } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import {
  GiftedChat,
  IMessage,
  Send,
  SendProps,
  SystemMessage,
} from 'react-native-gifted-chat';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import CustomActions from '@/components/chat/CustomActions';
import CustomView from '@/components/chat/CustomView';
import * as Clipboard from 'expo-clipboard';
import {
  GCUser,
  GCState,
  GCStateAction,
  ActionKind,
  User,
  Message,
  NotificationMessage,
} from '@/constants/Types';
import {getMessages, sendMessage} from '@/constants/APIs';
import { getUser, getPartner } from '@/components/services/SecureStore';
import { sendPushNotification } from '@/components/services/notifications/PushNotificationManager';

// Reducer function for managing state
const reducer = (state: GCState, action: GCStateAction) => {
  switch (action.type) {
    case ActionKind.SEND_MESSAGE: {
      return {
        ...state,
        messages: action.payload 
      };
    }
    case ActionKind.LOAD_EARLIER_MESSAGES: {
      return {
        ...state,
        loadEarlier: true,
        isLoadingEarlier: false,
        messages: action.payload
      };
    }
    case ActionKind.LOAD_EARLIER_START: {
      return { ...state, isLoadingEarlier: true }
    }
    case ActionKind.SET_IS_TYPING: {
      return { ...state, isTyping: action.payload }
    }
    default: return state;
  }
};

const MessagesScreen = () => {
  const [user, setUser] = useState<User | null>(null);
  const [partner, setPartner] = useState<User | null>(null);
  const [state, dispatch] = useReducer(reducer, {
    messages: [],
    loadEarlier: true,
    isLoadingEarlier: false,
    isTyping: false,
  });

  // Get user and partner data from SecureStore
  const msgUser: GCUser = {
    _id: user?.id ?? 0,
    name: user?.fullName ?? 'You',
    avatar: `${process.env.EXPO_PUBLIC_API_URL}${user?.pfpUrl}` ??
      `${process.env.EXPO_PUBLIC_API_URL}/images/default-profile.png`,
  };
  const msgPartner: GCUser = {
    _id: partner?.id ?? 0,
    name: partner?.fullName ?? 'Your Partner',
    avatar:  `${process.env.EXPO_PUBLIC_API_URL}${partner?.pfpUrl}` ?? 
      `${process.env.EXPO_PUBLIC_API_URL}/images/default-profile.png`,
  };

  // Initialize users & (trying to) fetch & display initial messages
  useEffect(() => {
    const initialize = async () => {
      try{
        const userData: User = await getUser() as User;
        const partnerData: User = await getPartner() as User;
        if (userData && partnerData) {
          setUser(userData);
          setPartner(partnerData);
        } else
          throw new Error('User or partner not found');
      } catch (error) {
        console.error('Error initializing users:', error);
        Alert.alert('Error', 'Failed to initialize users. Please try again.');
      }
    };
    initialize();
  }, []);

  useEffect(() => {
    if (!user || !partner) {
      console.log('User or partner not found');
      return;
    }
     const socket = io(process.env.EXPO_PUBLIC_WEBSOCKET_URL as string, {
      transports: ['websocket'],
    });
    socket.on('connect', () => {
      console.log('Connected to WebSocket server');
      socket.emit('join', user.id);
    });
    socket.on('connect_error', (error) => {
      console.error('Error connecting to WebSocket server:', error);
    });
    socket.on('message', async (newMessage) => {
      const initialMessages = await getMessages(user.id, 20, 0);
      if (!initialMessages) return;
      const formattedMessages = formatMessages(initialMessages);
      dispatch({
        type: ActionKind.SEND_MESSAGE,
        payload: formattedMessages,
      });
    });
    return () => {
      socket.disconnect();
    };   
  }, [user, partner]);

  useEffect(() => {
    const fetchMessages = async () => {
      if (user && partner) {
        dispatch({ type: ActionKind.LOAD_EARLIER_START });
        const initialMessages = await getMessages(user.id);
        if (initialMessages) {
          const formattedMessages = formatMessages(initialMessages);
          dispatch({ type: ActionKind.LOAD_EARLIER_MESSAGES, payload: formattedMessages });
        }
      }
    };
    fetchMessages();
  }, [user, partner]);

  // Format messages for GiftedChat
  const formatMessages = (dbMessages: Message[]): IMessage[] => {
    if (!user || !partner) return [];
    return dbMessages.map((msg) => ({
      _id: msg.id,
      text: msg.text,
      createdAt: new Date(msg.createdAt),
      user: msg.senderId === user.id ? msgUser : msgPartner,
    })) as IMessage[];
  };

  /* -------- Send message function -------- */
  const onSend = useCallback(async (messages: any[]) => {
    if (!user || !partner) return;

    // Prepare the message to be sent
    const createdAt = new Date();
    const tempId = Math.round(Math.random() * -1000000); // Temporary ID

    const messageToSend: Message = {
      id: tempId,
      senderId: user.id,
      receiverId: partner?.id ?? 0,
      text: messages[0].text,
      createdAt,
      isRead: false,
      hasLocation: false,
      hasMedia: false,
      hasQuickReply: false,
    };
    const notificationMessage: NotificationMessage = {
      sound: 'default',
      title: 'New message from ' + user.fullName,
      body: messageToSend.text,
      data: {
        message: messageToSend,
      },
    };
    sendPushNotification(partner.pushToken, notificationMessage);

    // Add the message with a tempId immediately to the state
    const tempFormattedMessages = formatMessages([messageToSend]);
    const updatedMessages = GiftedChat.append(state.messages, tempFormattedMessages);
    dispatch({
      type: ActionKind.SEND_MESSAGE,
      payload: updatedMessages,
    });

    try {
      // Send the message to the server
      const sentMessage = await sendMessage(messageToSend);
      if (!user || isNaN(user.id)) {
        console.error('User not found');
        throw new Error('User not found');
      }
      // Fetch the latest messages from the server to ensure consistency
      const updatedMessages = await getMessages(user.id);
      if (updatedMessages) {
        const formattedMessages = formatMessages(updatedMessages);
        dispatch({ type: ActionKind.SEND_MESSAGE, payload: formattedMessages });
      }
      
    } catch (error) {
      console.error('Error sending message:', error);
      // In case of an error, remove the temporary message from the state
      const updatedMessages = state.messages.filter((msg) => msg._id !== tempId);
      dispatch({ type: ActionKind.SEND_MESSAGE, payload: updatedMessages });
      Alert.alert('Error', 'Failed to send message. Please try again.');
    }
  }, [user, partner, state.messages]);

  const onLoadEarlier = useCallback(async () => {
    if (!user) {
      console.log('User not found');
      return;
    }
    // Set loading state
    dispatch({ type: ActionKind.LOAD_EARLIER_START });
    // Fetch the current size of messages already in chat to calculate the new offset
    const offset = state.messages.length;
    try {
      const earlierMessages = await getMessages(user.id, 20, offset);
      if (earlierMessages) {
        const formattedMessages = formatMessages(earlierMessages);
        const updatedMessages = GiftedChat.prepend(state.messages, formattedMessages);
        dispatch({ type: ActionKind.LOAD_EARLIER_MESSAGES, payload: updatedMessages });
      }
    } catch (error) {
      console.error('Error loading earlier messages:', error);
      dispatch({ type: ActionKind.LOAD_EARLIER_MESSAGES, payload: [] });
    }
  }, [user, state.messages]);

  const parsePatterns = useCallback(() => {
    return [
      {
        pattern: /#(\w+)/g,
        style: { textDecorationLine: 'underline', color: 'darkorange' },
        onPress: () => Linking.openURL('https://www.gbrown.org'),
      },
    ]
  }, []);

  const onLongPressAvatar = useCallback((pressedUser: any) => {
    Alert.alert(JSON.stringify(pressedUser))
  }, []);

  const onPressAvatar = useCallback(() => {
    Alert.alert('Pressed avatar!')
  }, []);

  const handleLongPress = useCallback((context: unknown, currentMessage: object) => {
    if (!currentMessage.text) return;
    const options = [
      'Copy text',
      'Cancel',
    ]
    const cancelButtonIndex = options.length - 1;
    (context as any).actionSheet().showActionSheetWithOptions(
      { options, cancelButtonIndex },
      (buttonIndex: number) => {
        switch (buttonIndex) {
          case 0:
            Clipboard.setStringAsync(currentMessage.text);
            break;
          default: break;
        }
      }
    )
  }, []);

  const onQuickReply = useCallback((replies: any[]) => {
    const createdAt = new Date();
    if (replies.length === 1)
      onSend([
        {
          createdAt,
          _id: Math.round(Math.random() * 1000000),
          text: replies[0].title,
          user: msgUser,
        },
      ]);
    else if (replies.length > 1)
      onSend([
        {
          createdAt,
          _id: Math.round(Math.random() * 1000000),
          text: replies.map(reply => reply.title).join(', '),
          user: msgUser,
        },
      ]);
    else console.warn('replies param is not set correctly');
  }, [msgUser, onSend]);

  const renderQuickReplySend = useCallback(() => {
    return <ThemedText>{'custom send =>'}</ThemedText>
  }, []);

  const setIsTyping = useCallback((isTyping: boolean) => {
    dispatch({ type: ActionKind.SET_IS_TYPING, payload: isTyping });
  }, [dispatch]);

  const renderCustomActions = useCallback(
    props =>
      Platform.OS === 'web' ? null : (
        <CustomActions {...props} onSend={onSend} />
      ),
    [onSend]
  );

  const renderSystemMessage = useCallback(props => {
    return (
      <SystemMessage
        {...props}
        containerStyle={{marginBottom: 15}}
        textStyle={{fontSize: 14}}
      />
    );
  }, []);

  const renderCustomView = useCallback(props => {
    return <CustomView {...props} />
  }, []);

  const renderSend = useCallback((props: SendProps<IMessage>) => {
    return (
      <Send
        {...props}
        containerStyle={{justifyContent: 'center', paddingHorizontal: 10}}
      >
        <MaterialIcons size={30} color={'tomato'} name={'send'} />
      </Send>
    );
  }, []);

  return (
    <SafeAreaView style={styles.fill}>
      <ThemedView style={styles.fill}>
        <GiftedChat
          messages={state.messages}
          onSend={onSend}
          loadEarlier={state.loadEarlier}
          onLoadEarlier={onLoadEarlier}
          isLoadingEarlier={state.isLoadingEarlier}
          parsePatterns={parsePatterns}
          user={msgUser}
          scrollToBottom
          onPressAvatar={onPressAvatar}
          onLongPressAvatar={onLongPressAvatar}
          onLongPress={handleLongPress}
          onQuickReply={onQuickReply}
          quickReplyStyle={{ borderRadius: 2 }}
          quickReplyTextStyle={{
            fontWeight: '200',
          }}
          renderQuickReplySend={renderQuickReplySend}
          renderActions={renderCustomActions}
          renderSystemMessage={renderSystemMessage}
          renderCustomView={renderCustomView}
          renderSend={renderSend}
          keyboardShouldPersistTaps='never'
          timeTextStyle={{
            left: { color: 'red' },
            right: { color: 'yellow' },
          }}
          isTyping={state.isTyping}
          inverted={Platform.OS !== 'web'}
          infiniteScroll
        />
      </ThemedView>
    </SafeAreaView>
  )
};

const ChatWrapper = () => {
  return (
    <SafeAreaProvider>
      <MessagesScreen />
    </SafeAreaProvider>
  );
};

export default ChatWrapper;

const styles = StyleSheet.create({
  fill: {
    flex: 1,
  },
});
