import React, {
  createContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  useCallback,
} from 'react';
import {
  addDataToCollection,
  getDataFromCollection,
  syncDatabasePayment,
} from '../db';
import {
  decryptMessage,
  encriptMessage,
} from '../app/functions/messaging/encodingAndDecodingMessages';

import {
  clearContactRaceRetryTimers,
  CONTACTS_TRANSACTION_UPDATE_NAME,
  contactsSQLEventEmitter,
  deleteCachedMessages,
  getCachedMessages,
  queueSetCashedMessages,
  startContactPaymentMatchRetrySequance,
} from '../app/functions/messaging/cachedMessages';
import { db } from '../db/initializeFirebase';
import { useKeysContext } from './keys';
import {
  collection,
  onSnapshot,
  or,
  orderBy,
  query,
  where,
} from '@react-native-firebase/firestore';
import { getCachedProfileImage } from '../app/functions/cachedImage';
import { useAuthContext } from './authContext';

// Create a context for the WebView ref
const GlobalContacts = createContext(null);

export const GlobalContactsList = ({ children }) => {
  const { authResetkey } = useAuthContext();
  const { contactsPrivateKey, publicKey } = useKeysContext();
  const [globalContactsInformation, setGlobalContactsInformation] = useState(
    {},
  );
  const [contactsMessags, setContactsMessagses] = useState({});
  const lookForNewMessages = useRef(true);
  const unsubscribeMessagesRef = useRef(null);
  const isInitialLoad = useRef(true);
  const addedContacts = globalContactsInformation.addedContacts;

  const toggleGlobalContactsInformation = useCallback(
    (newData, writeToDB) => {
      setGlobalContactsInformation(prev => {
        const newContacts = { ...prev, ...newData };
        if (writeToDB) {
          addDataToCollection(
            { contacts: newContacts },
            'blitzWalletUsers',
            publicKey,
          );
        }
        return newContacts;
      });
    },
    [publicKey],
  );

  const decodedAddedContacts = useMemo(() => {
    if (!publicKey || !addedContacts) return [];

    if (typeof addedContacts !== 'string') return [];

    try {
      const decryptedData = decryptMessage(
        contactsPrivateKey,
        publicKey,
        addedContacts,
      );

      return [...JSON.parse(decryptedData)];
    } catch (error) {
      console.warn('Failed to parse addedContacts.', error);
      return [];
    }
  }, [addedContacts, publicKey, contactsPrivateKey]);

  const updatedCachedMessagesStateFunction = useCallback(async () => {
    if (!Object.keys(globalContactsInformation).length || !contactsPrivateKey)
      return;
    const savedMessages = await getCachedMessages();
    setContactsMessagses(savedMessages);
    const unknownContacts = await Promise.all(
      Object.keys(savedMessages)
        .filter(key => key !== 'lastMessageTimestamp')
        .filter(
          contact =>
            !decodedAddedContacts.find(
              contactElement => contactElement.uuid === contact,
            ) && contact !== globalContactsInformation.myProfile.uuid,
        )
        .map(contact => getDataFromCollection('blitzWalletUsers', contact)),
    );

    const newContats = unknownContacts
      .filter(
        retrivedContact =>
          retrivedContact &&
          retrivedContact.uuid !== globalContactsInformation.myProfile.uuid,
      )
      .map(retrivedContact => ({
        bio: retrivedContact.contacts.myProfile.bio || '',
        isFavorite: false,
        name: retrivedContact.contacts.myProfile.name,
        receiveAddress: retrivedContact.contacts.myProfile.receiveAddress,
        uniqueName: retrivedContact.contacts.myProfile.uniqueName,
        uuid: retrivedContact.contacts.myProfile.uuid,
        isAdded: false,
        unlookedTransactions: 0,
      }));

    if (newContats.length > 0) {
      await Promise.allSettled(
        newContats.map(contact => getCachedProfileImage(contact.uuid)),
      );

      toggleGlobalContactsInformation(
        {
          myProfile: { ...globalContactsInformation.myProfile },
          addedContacts: encriptMessage(
            contactsPrivateKey,
            globalContactsInformation.myProfile.uuid,
            JSON.stringify(decodedAddedContacts.concat(newContats)),
          ),
        },
        true,
      );
    }
  }, [globalContactsInformation, decodedAddedContacts, contactsPrivateKey]);

  useEffect(() => {
    async function handleUpdate(updateType) {
      try {
        console.log('Received contact transaction update type', updateType);
        if (updateType === 'hanleContactRace') {
          // If the update type is "handle race", we have not yet received the payment
          // event from Spark. A race condition can occur where a Firebase DB event
          // fires at the same time the corresponding Spark transaction is saved to
          // Spark’s SQL database. In this case, the description and contact information
          // from Firebase may not be attached to the Spark transaction.
          //
          // To mitigate this, we use an exponential backoff that repeatedly checks
          // saved transactions (specifically incoming transactions that do not yet
          // have a corresponding Spark transaction). This reduces the chance that
          // the transaction is saved without the associated metadata.
          startContactPaymentMatchRetrySequance();
          return;
        }
        updatedCachedMessagesStateFunction();
      } catch (err) {
        console.log('error in contact messages update function', err);
      }
    }
    contactsSQLEventEmitter.removeAllListeners(
      CONTACTS_TRANSACTION_UPDATE_NAME,
    );
    contactsSQLEventEmitter.on(CONTACTS_TRANSACTION_UPDATE_NAME, handleUpdate);

    return () => {
      contactsSQLEventEmitter.removeAllListeners(
        CONTACTS_TRANSACTION_UPDATE_NAME,
      );
      clearContactRaceRetryTimers();
    };
  }, [updatedCachedMessagesStateFunction]);

  const updateContactUniqueName = useCallback(
    async newUniqueNames => {
      try {
        if (newUniqueNames.size === 0) {
          return;
        }

        setGlobalContactsInformation(prev => {
          try {
            // Validate prerequisites
            if (!contactsPrivateKey || !publicKey) {
              console.warn('Missing required data for contact update');
              return prev;
            }

            let currentContacts;
            try {
              const decryptedData = decryptMessage(
                contactsPrivateKey,
                publicKey,
                prev.addedContacts,
              );

              if (!decryptedData) {
                console.warn('Decryption returned empty data');
                return prev;
              }

              currentContacts = JSON.parse(decryptedData);

              // Validate parsed data
              if (!Array.isArray(currentContacts)) {
                console.warn('Decrypted contacts is not an array');
                return prev;
              }
            } catch (decryptError) {
              console.error(
                'Failed to decode contacts for update:',
                decryptError,
              );
              return prev;
            }

            let hasChanges = false;
            const updatedContacts = currentContacts.map(contact => {
              try {
                const newUniqueName = newUniqueNames.get(contact.uuid);

                if (
                  newUniqueName &&
                  typeof newUniqueName === 'string' &&
                  newUniqueName.trim() !== '' &&
                  newUniqueName !== contact.uniqueName
                ) {
                  hasChanges = true;
                  return {
                    ...contact,
                    uniqueName: newUniqueName,
                  };
                }

                return contact;
              } catch (mapError) {
                console.error('Error processing contact:', mapError);
                return contact;
              }
            });

            if (!hasChanges) {
              return prev;
            }

            try {
              const newEncryptedContacts = encriptMessage(
                contactsPrivateKey,
                publicKey,
                JSON.stringify(updatedContacts),
              );

              if (!newEncryptedContacts) {
                console.error('Encryption failed, aborting update');
                return prev;
              }

              addDataToCollection(
                {
                  contacts: {
                    ...prev,
                    addedContacts: newEncryptedContacts,
                  },
                },
                'blitzWalletUsers',
                publicKey,
              ).catch(dbError => {
                console.error('Failed to save contacts to database:', dbError);
              });

              return {
                ...prev,
                addedContacts: newEncryptedContacts,
              };
            } catch (encryptError) {
              console.error(
                'Failed to encrypt updated contacts:',
                encryptError,
              );
              return prev;
            }
          } catch (stateError) {
            console.error('Error in state update function:', stateError);
            return prev;
          }
        });
      } catch (outerError) {
        console.error('Critical error in updateContactUniqueName:', outerError);
      }
    },
    [contactsPrivateKey, publicKey],
  );

  useEffect(() => {
    if (!Object.keys(globalContactsInformation).length) return;
    if (!contactsPrivateKey) return;
    // Set timestamp to last conversation to retrive historical messages
    async function handleListener() {
      const cachedConversations = await getCachedMessages();
      const savedMillis = cachedConversations.lastMessageTimestamp;

      // Unsubscribe from previous listeners before setting new ones
      if (unsubscribeMessagesRef.current) {
        unsubscribeMessagesRef.current();
      }

      const combinedMessageQuery = query(
        collection(db, 'contactMessages'),
        where('timestamp', '>', savedMillis),
        or(
          where('toPubKey', '==', globalContactsInformation.myProfile.uuid),
          where('fromPubKey', '==', globalContactsInformation.myProfile.uuid),
        ),
        orderBy('timestamp'),
      );

      unsubscribeMessagesRef.current = onSnapshot(
        combinedMessageQuery,
        snapshot => {
          if (!snapshot?.docChanges()?.length) return;
          let newMessages = [];
          let newUniqueIds = new Map();
          snapshot.docChanges().forEach(change => {
            if (change.type === 'added') {
              const newMessage = change.doc.data();
              // Log whether it's sent or received
              const isReceived =
                newMessage.toPubKey ===
                globalContactsInformation.myProfile.uuid;
              console.log(
                `${isReceived ? 'received' : 'sent'} a new message`,
                newMessage,
              );
              if (typeof newMessage.message === 'string') {
                const sendersPubkey =
                  newMessage.toPubKey ===
                  globalContactsInformation.myProfile.uuid
                    ? newMessage.fromPubKey
                    : newMessage.toPubKey;
                const decoded = decryptMessage(
                  contactsPrivateKey,
                  sendersPubkey,
                  newMessage.message,
                );

                if (!decoded) return;
                let parsedMessage;
                try {
                  parsedMessage = JSON.parse(decoded);
                } catch (err) {
                  console.log('error parsing decoded message', err);
                  return;
                }

                if (parsedMessage?.senderProfileSnapshot && isReceived) {
                  newUniqueIds.set(
                    sendersPubkey,
                    parsedMessage.senderProfileSnapshot?.uniqueName,
                  );
                }

                newMessages.push({
                  ...newMessage,
                  message: parsedMessage,
                  sendersPubkey,
                  isReceived,
                });
              } else newMessages.push(newMessage);
            }
          });
          updateContactUniqueName(newUniqueIds);
          if (newMessages.length > 0) {
            queueSetCashedMessages({
              newMessagesList: newMessages,
              myPubKey: globalContactsInformation.myProfile.uuid,
            });
          }
        },
      );
    }

    handleListener();

    return () => {
      if (unsubscribeMessagesRef.current) {
        unsubscribeMessagesRef.current();
      }
    };
  }, [globalContactsInformation?.myProfile?.uuid, contactsPrivateKey]);

  const addContact = useCallback(
    async contact => {
      try {
        const newContact = {
          name: contact.name || '',
          nameLower: contact.nameLower || '',
          bio: contact.bio,
          unlookedTransactions: 0,
          isLNURL: contact.isLNURL,
          uniqueName: contact.uniqueName || '',
          uuid: contact.uuid,
          isAdded: true,
          isFavorite: false,
          profileImage: contact.profileImage,
          receiveAddress: contact.receiveAddress,
          transactions: [],
        };

        let newAddedContacts = JSON.parse(JSON.stringify(decodedAddedContacts));

        const isContactInAddedContacts = newAddedContacts.filter(
          addedContact => addedContact.uuid === newContact.uuid,
        ).length;

        if (isContactInAddedContacts) {
          newAddedContacts = newAddedContacts.map(addedContact => {
            if (addedContact.uuid === newContact.uuid) {
              return {
                ...addedContact,
                name: newContact.name,
                nameLower: newContact.nameLower,
                bio: newContact.bio,
                unlookedTransactions: 0,
                isAdded: true,
              };
            } else return addedContact;
          });
        } else newAddedContacts.push(newContact);

        toggleGlobalContactsInformation(
          {
            myProfile: {
              ...globalContactsInformation.myProfile,
              didEditProfile: true,
            },
            addedContacts: encriptMessage(
              contactsPrivateKey,
              publicKey,
              JSON.stringify(newAddedContacts),
            ),
          },
          true,
        );
      } catch (err) {
        console.log('Error adding  contact', err);
      }
    },
    [
      decodedAddedContacts,
      contactsPrivateKey,
      publicKey,
      globalContactsInformation,
    ],
  );

  const deleteContact = useCallback(
    async contact => {
      try {
        const newAddedContacts = decodedAddedContacts
          .map(savedContacts => {
            if (savedContacts.uuid === contact.uuid) {
              return null;
            } else return savedContacts;
          })
          .filter(contact => contact);

        await deleteCachedMessages(contact.uuid);

        toggleGlobalContactsInformation(
          {
            addedContacts: encriptMessage(
              contactsPrivateKey,
              publicKey,
              JSON.stringify(newAddedContacts),
            ),
            myProfile: { ...globalContactsInformation.myProfile },
          },
          true,
        );
      } catch (err) {
        console.log('Error deleating contact', err);
      }
    },
    [
      decodedAddedContacts,
      contactsPrivateKey,
      publicKey,
      globalContactsInformation,
    ],
  );

  useEffect(() => {
    if (!Object.keys(globalContactsInformation).length) return;
    if (!contactsPrivateKey) return;
    // No longer need to sync here since we are handling this through snapshot
    updatedCachedMessagesStateFunction();
    return;

    if (lookForNewMessages.current) {
      lookForNewMessages.current = false;
      async function handleOfflineMessageSync() {
        const restoredPayments = await syncDatabasePayment(
          globalContactsInformation.myProfile.uuid,
          contactsPrivateKey,
        );

        if (restoredPayments.length === 0) {
          updatedCachedMessagesStateFunction();
        }

        const contactDataMap = new Map(
          restoredPayments
            .filter(
              item =>
                item.isReceived &&
                item?.message?.senderProfileSnapshot?.uniqueName,
            )
            .map(item => [
              item.sendersPubkey,
              item.message.senderProfileSnapshot.uniqueName,
            ]),
        );
        updateContactUniqueName(contactDataMap);

        queueSetCashedMessages({
          newMessagesList: restoredPayments,
          myPubKey: globalContactsInformation.myProfile.uuid,
        });
      }
      handleOfflineMessageSync();
    }
  }, [
    globalContactsInformation,
    updatedCachedMessagesStateFunction,
    contactsPrivateKey,
  ]);

  const giftCardsList = useMemo(() => {
    if (!contactsMessags) return [];

    const actualContacts = Object.keys(contactsMessags);
    const lastMessageTimestampIndex = actualContacts.indexOf(
      'lastMessageTimestamp',
    );

    // Remove lastMessageTimestamp efficiently
    if (lastMessageTimestampIndex > -1) {
      actualContacts.splice(lastMessageTimestampIndex, 1);
    }

    if (actualContacts.length === 0) return [];

    const giftCards = [];

    // Process contacts efficiently
    for (const contact of actualContacts) {
      const contactData = contactsMessags[contact];
      if (!contactData?.messages?.length) continue;

      // Use for loop for better performance than filter + push
      const messages = contactData.messages;
      for (let i = 0; i < messages.length; i++) {
        const message = messages[i];
        if (message.message?.giftCardInfo && !message.message.didSend) {
          giftCards.push(message);
        }
      }
    }

    // Sort in-place for memory efficiency
    giftCards.sort((a, b) => {
      const timeA = a.serverTimestamp || a.timestamp;
      const timeB = b.serverTimestamp || b.timestamp;
      return timeB - timeA;
    });

    return giftCards;
  }, [contactsMessags]);

  const hasUnlookedTransactions = useMemo(() => {
    return Object.keys(contactsMessags).some(contactUUID => {
      if (
        contactUUID === 'lastMessageTimestamp' ||
        contactUUID === globalContactsInformation?.myProfile?.uuid
      ) {
        return false;
      }
      const messages = contactsMessags[contactUUID]?.messages;
      return messages?.some(message => !message.message.wasSeen) || false;
    });
  }, [contactsMessags, globalContactsInformation?.myProfile?.uuid]);

  useEffect(() => {
    if (isInitialLoad.current) {
      isInitialLoad.current = false;
      return;
    }

    if (unsubscribeMessagesRef.current) {
      unsubscribeMessagesRef.current();
      unsubscribeMessagesRef.current = null;
    }
    lookForNewMessages.current = true;
  }, [authResetkey]);

  const contextValue = useMemo(
    () => ({
      decodedAddedContacts,
      globalContactsInformation,
      toggleGlobalContactsInformation,
      contactsMessags,
      updatedCachedMessagesStateFunction,
      giftCardsList,
      hasUnlookedTransactions,
      deleteContact,
      addContact,
    }),
    [
      decodedAddedContacts,
      globalContactsInformation,
      toggleGlobalContactsInformation,
      contactsMessags,
      updatedCachedMessagesStateFunction,
      giftCardsList,
      hasUnlookedTransactions,
      deleteContact,
      addContact,
    ],
  );

  return (
    <GlobalContacts.Provider value={contextValue}>
      {children}
    </GlobalContacts.Provider>
  );
};

export const useGlobalContacts = () => {
  return React.useContext(GlobalContacts);
};
