import { useMemo } from 'react';
import { Alert } from 'react-native';

import { UserResponse } from 'stream-chat';

import { useUserMuteActive } from './useUserMuteActive';

import { useScreenReaderEnabled } from '../../../a11y/hooks/useScreenReaderEnabled';
import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext';
import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext';
import { MessageComposerAPIContextValue } from '../../../contexts/messageComposerContext/MessageComposerAPIContext';
import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext';
import { useMessageInputContext } from '../../../contexts/messageInputContext/MessageInputContext';
import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';

import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
import {
  useAfterKeyboardOpenCallback,
  usePortalSettledCallback,
  useStableCallback,
} from '../../../hooks';
import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage';
import { NativeHandlers } from '../../../native';
import { useNotificationApi } from '../../Notifications';

const getErrorMessage = (error: unknown, fallback: string) =>
  error instanceof Error && error.message ? error.message : fallback;

const getNotificationError = (error: unknown): Error | undefined => {
  if (error instanceof Error) return error;
  if (typeof error === 'string') return new Error(error);
  if (error && typeof error === 'object' && 'message' in error) {
    const message = error.message;
    if (typeof message === 'string') return new Error(message);
  }
  return undefined;
};

const getNotificationErrorOptions = (error: unknown) => {
  const originalError = getNotificationError(error);
  return originalError ? { originalError } : {};
};

export const useWithPortalKeyboardSafety = <T extends unknown[]>(
  callback: (...args: T) => void,
) => {
  const callbackAfterKeyboardOpen = useAfterKeyboardOpenCallback(callback);

  return usePortalSettledCallback(callbackAfterKeyboardOpen);
};

export const useMessageActionHandlers = ({
  channel,
  client,
  deleteMessage,
  deleteReaction,
  message,
  retrySendMessage,
  sendReaction,
  setEditingState,
  setQuotedMessage,
}: Pick<
  MessagesContextValue,
  'sendReaction' | 'deleteMessage' | 'deleteReaction' | 'retrySendMessage' | 'supportedReactions'
> &
  Pick<ChannelContextValue, 'channel' | 'enforceUniqueReaction'> &
  Pick<ChatContextValue, 'client'> &
  Pick<MessageContextValue, 'message'> &
  Pick<MessageComposerAPIContextValue, 'setEditingState' | 'setQuotedMessage'>) => {
  const { t } = useTranslationContext();
  const { addNotification } = useNotificationApi();
  const { inputBoxRef } = useMessageInputContext();
  const screenReaderEnabled = useScreenReaderEnabled();
  const handleResendMessage = useStableCallback(() => retrySendMessage(message));
  const translatedMessage = useTranslatedMessage(message);

  const isMuted = useUserMuteActive(message.user);

  const handleQuotedReplyMessage = useStableCallback(() => {
    setQuotedMessage(message);
    if (screenReaderEnabled) {
      inputBoxRef.current?.focus();
    }
  });

  const handleCopyMessage = useStableCallback(() => {
    if (!message.text) {
      return;
    }
    NativeHandlers.setClipboardString(translatedMessage?.text ?? message.text, {
      onFailure: (error) => {
        addNotification({
          message: t('Failed to copy message'),
          options: {
            ...getNotificationErrorOptions(error),
            severity: 'error',
            type: 'clipboard:message:copy:failed',
          },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      },
      onSuccess: () => {
        addNotification({
          message: t('Message copied to clipboard'),
          options: { severity: 'success', type: 'clipboard:message:copy:success' },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      },
    });
  });

  const handleDeleteMessage = useStableCallback(() => {
    if (!message.id) {
      return;
    }
    Alert.alert(
      t('Delete Message'),
      t('Are you sure you want to permanently delete this message?'),
      [
        { style: 'cancel', text: t('Cancel') },
        {
          onPress: async () => {
            try {
              await deleteMessage(message);
              addNotification({
                message: t('Message deleted'),
                options: { severity: 'success', type: 'api:message:delete:success' },
                origin: { context: { message }, emitter: 'MessageActions' },
              });
            } catch (error) {
              addNotification({
                message: getErrorMessage(error, t('Error deleting message')),
                options: {
                  ...getNotificationErrorOptions(error),
                  severity: 'error',
                  type: 'api:message:delete:failed',
                },
                origin: { context: { message }, emitter: 'MessageActions' },
              });
            }
          },
          style: 'destructive',
          text: t('Delete'),
        },
      ],
      { cancelable: false },
    );
  });

  const handleDeleteForMeMessage = useStableCallback(async () => {
    if (!message.id) {
      return;
    }

    await deleteMessage(message, { deleteForMe: true });
  });

  const handleToggleMuteUser = useStableCallback(async () => {
    if (!message.user?.id) {
      return;
    }

    try {
      if (isMuted) {
        await client.unmuteUser(message.user.id);
        addNotification({
          message: t('{{ user }} has been unmuted', {
            user: message.user?.name || message.user?.id,
          }),
          options: { severity: 'success', type: 'api:user:unmute:success' },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      } else {
        await client.muteUser(message.user.id);
        addNotification({
          message: t('{{ user }} has been muted', {
            user: message.user?.name || message.user?.id,
          }),
          options: { severity: 'success', type: 'api:user:mute:success' },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      }
    } catch (error) {
      addNotification({
        message: getErrorMessage(
          error,
          isMuted ? t('Error unmuting a user ...') : t('Error muting a user ...'),
        ),
        options: {
          ...getNotificationErrorOptions(error),
          severity: 'error',
          type: isMuted ? 'api:user:unmute:failed' : 'api:user:mute:failed',
        },
        origin: { context: { message }, emitter: 'MessageActions' },
      });
    }
  });

  const handleToggleBanUser = useStableCallback(async () => {
    const messageUser = message.user;
    if (!messageUser) {
      return;
    }

    if (messageUser.banned) {
      await client.unbanUser(messageUser.id);
    } else {
      await client.banUser(messageUser.id);
    }
  });

  const handleTogglePinMessage = useStableCallback(async () => {
    const isPinned = !!message.pinned;
    try {
      if (!isPinned) {
        await client.pinMessage(message, null);
        addNotification({
          message: t('Message pinned'),
          options: { severity: 'success', type: 'api:message:pin:success' },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      } else {
        await client.unpinMessage(message);
        addNotification({
          message: t('Message unpinned'),
          options: { severity: 'success', type: 'api:message:unpin:success' },
          origin: { context: { message }, emitter: 'MessageActions' },
        });
      }
    } catch (error) {
      addNotification({
        message: getErrorMessage(
          error,
          isPinned ? t('Error removing message pin') : t('Error pinning message'),
        ),
        options: {
          ...getNotificationErrorOptions(error),
          severity: 'error',
          type: isPinned ? 'api:message:unpin:failed' : 'api:message:pin:failed',
        },
        origin: { context: { message }, emitter: 'MessageActions' },
      });
    }
  });

  const handleEditMessage = useWithPortalKeyboardSafety(() => {
    setEditingState(message);
  });

  const handleFlagMessage = useStableCallback(() => {
    if (!message.id) {
      return;
    }
    Alert.alert(
      t('Flag Message'),
      t('Do you want to send a copy of this message to a moderator for further investigation?'),
      [
        { style: 'cancel', text: t('Cancel') },
        {
          onPress: async () => {
            try {
              await client.flagMessage(message.id);
              addNotification({
                message: t('Message has been successfully flagged'),
                options: { severity: 'success', type: 'api:message:flag:success' },
                origin: { context: { message }, emitter: 'MessageActions' },
              });
            } catch (error) {
              addNotification({
                message: getErrorMessage(error, t('Error adding flag')),
                options: {
                  ...getNotificationErrorOptions(error),
                  severity: 'error',
                  type: 'api:message:flag:failed',
                },
                origin: { context: { message }, emitter: 'MessageActions' },
              });
            }
          },
          text: t('Flag'),
        },
      ],
      { cancelable: false },
    );
  });

  const handleMarkUnreadMessage = useStableCallback(async () => {
    if (!message.id) {
      return;
    }
    try {
      await channel.markUnread({ message_id: message.id });
      addNotification({
        message: t('Message marked as unread'),
        options: { severity: 'success', type: 'api:message:markUnread:success' },
        origin: { context: { message }, emitter: 'MessageActions' },
      });
    } catch (error) {
      addNotification({
        message: getErrorMessage(
          error,
          t(
            'Error marking message unread. Cannot mark unread messages older than the newest 100 channel messages.',
          ),
        ),
        options: {
          ...getNotificationErrorOptions(error),
          severity: 'error',
          type: 'api:message:markUnread:failed',
        },
        origin: { context: { message }, emitter: 'MessageActions' },
      });
    }
  });

  const handleToggleReaction = useStableCallback(async (reactionType: string) => {
    const messageId = message.id;
    const own_reactions = message.own_reactions ?? [];
    const userExistingReaction = own_reactions.find((reaction) => {
      // own user should only ever contain the current user id
      // just in case we check to prevent bugs with message updates from breaking reactions
      if (reaction.user && client.userID === reaction.user.id && reaction.type === reactionType) {
        return true;
      } else if (reaction.user && client.userID !== reaction.user.id) {
        console.warn(
          'message.own_reactions contained reactions from a different user, this indicates a bug',
        );
      }
      return false;
    });
    // Change reaction in local state, make API call in background, revert to old message if fails
    try {
      if (channel && messageId) {
        if (userExistingReaction) {
          await deleteReaction(userExistingReaction.type, messageId);
        } else {
          await sendReaction(reactionType, messageId);
        }
      }
    } catch (err) {
      console.log(err);
    }
  });

  const handleToggleBlockUser = useStableCallback(async (user: UserResponse | null | undefined) => {
    try {
      if (!user) {
        return;
      }
      const isBlocked = new Set(client.blockedUsers.getLatestValue().userIds).has(user?.id ?? '');
      if (isBlocked) {
        await client.unBlockUser(user.id);
      } else {
        await client.blockUser(user.id);
      }
    } catch (err) {
      console.log(err);
    }
  });

  return useMemo(
    () => ({
      handleCopyMessage,
      handleDeleteForMeMessage,
      handleDeleteMessage,
      handleEditMessage,
      handleFlagMessage,
      handleMarkUnreadMessage,
      handleQuotedReplyMessage,
      handleResendMessage,
      handleToggleBanUser,
      handleToggleMuteUser,
      handleTogglePinMessage,
      handleToggleReaction,
      handleToggleBlockUser,
    }),
    [
      handleCopyMessage,
      handleDeleteForMeMessage,
      handleDeleteMessage,
      handleEditMessage,
      handleFlagMessage,
      handleMarkUnreadMessage,
      handleQuotedReplyMessage,
      handleResendMessage,
      handleToggleBanUser,
      handleToggleMuteUser,
      handleTogglePinMessage,
      handleToggleReaction,
      handleToggleBlockUser,
    ],
  );
};
