import {
  addActionHandler, getActions, getGlobal, setGlobal,
} from '../../index';

import type { ApiChat, ApiContact, ApiUser } from '../../../api/types';
import type { InlineBotSettings } from '../../../types';

import {
  RE_TG_LINK, RE_TME_LINK,
} from '../../../config';
import { callApi } from '../../../api/gramjs';
import {
  selectBot,
  selectChat, selectChatBot, selectChatMessage, selectCurrentChat, selectCurrentMessageList,
  selectIsTrustedBot, selectReplyingToId, selectSendAs, selectUser,
} from '../../selectors';
import { addChats, addUsers, removeBlockedContact } from '../../reducers';
import { buildCollectionByKey } from '../../../util/iteratees';
import { debounce } from '../../../util/schedulers';
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
import { getServerTime } from '../../../util/serverTime';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import PopupManager from '../../../util/PopupManager';

const GAMEE_URL = 'https://prizes.gamee.com/';
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
const runDebouncedForSearch = debounce((cb) => cb(), 500, false);

addActionHandler('clickBotInlineButton', (global, actions, payload) => {
  const { messageId, button } = payload;

  switch (button.type) {
    case 'command':
      actions.sendBotCommand({ command: button.text });
      break;
    case 'url': {
      const { url } = button;
      if (url.match(RE_TME_LINK) || url.match(RE_TG_LINK)) {
        actions.openTelegramLink({ url });
      } else {
        actions.toggleSafeLinkModal({ url });
      }
      break;
    }
    case 'callback': {
      const chat = selectCurrentChat(global);
      if (!chat) {
        return;
      }

      void answerCallbackButton(chat, messageId, button.data);
      break;
    }
    case 'requestPoll':
      actions.openPollModal({ isQuiz: button.isQuiz });
      break;
    case 'requestPhone': {
      const user = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
      if (!user) {
        return;
      }
      actions.showDialog({
        data: {
          phoneNumber: user.phoneNumber,
          firstName: user.firstName,
          lastName: user.lastName,
          userId: user.id,
        } as ApiContact,
      });
      break;
    }
    case 'receipt': {
      const chat = selectCurrentChat(global);
      if (!chat) {
        return;
      }
      const { receiptMessageId } = button;
      actions.getReceipt({ receiptMessageId, chatId: chat.id, messageId });
      break;
    }
    case 'buy': {
      const chat = selectCurrentChat(global);
      if (!chat) {
        return;
      }

      actions.getPaymentForm({ chat, messageId });
      actions.setInvoiceMessageInfo(selectChatMessage(global, chat.id, messageId));
      actions.openPaymentModal({ chatId: chat.id, messageId });
      break;
    }
    case 'game': {
      const chat = selectCurrentChat(global);
      if (!chat) {
        return;
      }

      void answerCallbackButton(chat, messageId, undefined, true);
      break;
    }
    case 'switchBotInline': {
      const { query, isSamePeer } = button;
      actions.switchBotInline({ query, isSamePeer, messageId });
      break;
    }

    case 'userProfile': {
      const { userId } = button;
      actions.openChatWithInfo({ id: userId });
      break;
    }

    case 'simpleWebView': {
      const { url } = button;
      const { chatId } = selectCurrentMessageList(global) || {};
      if (!chatId) {
        return;
      }
      const bot = selectBot(global, chatId);
      if (!bot) {
        return;
      }
      const theme = extractCurrentThemeParams();
      actions.requestSimpleWebView({
        url, bot, theme, buttonText: button.text,
      });
      break;
    }

    case 'webView': {
      const { url } = button;
      const chat = selectCurrentChat(global);
      if (!chat) {
        return;
      }
      const message = selectChatMessage(global, chat.id, messageId);
      if (!message) {
        return;
      }
      if (!message.viaBotId && !message.senderId) {
        return;
      }
      const bot = selectBot(global, message.viaBotId! || message.senderId!);
      if (!bot) {
        return;
      }
      const theme = extractCurrentThemeParams();
      actions.requestWebView({
        url,
        bot,
        peer: chat,
        theme,
        buttonText: button.text,
      });
      break;
    }
  }
});

addActionHandler('sendBotCommand', (global, actions, payload) => {
  const { command, chatId } = payload;
  const { currentUserId } = global;
  const chat = chatId ? selectChat(global, chatId) : selectCurrentChat(global);
  const currentMessageList = selectCurrentMessageList(global);

  if (!currentUserId || !chat || !currentMessageList) {
    return;
  }

  const { threadId } = currentMessageList;
  actions.setReplyingToId({ messageId: undefined });
  actions.clearWebPagePreview({ chatId: chat.id, threadId, value: false });

  void sendBotCommand(
    chat, currentUserId, command, selectReplyingToId(global, chat.id, threadId), selectSendAs(global, chatId),
  );
});

addActionHandler('restartBot', async (global, actions, payload) => {
  const { chatId } = payload;
  const { currentUserId } = global;
  const chat = selectCurrentChat(global);
  const bot = currentUserId && selectChatBot(global, chatId);
  if (!currentUserId || !chat || !bot) {
    return;
  }

  const result = await callApi('unblockContact', bot.id, bot.accessHash);
  if (!result) {
    return;
  }

  setGlobal(removeBlockedContact(getGlobal(), bot.id));
  void sendBotCommand(chat, currentUserId, '/start', undefined, selectSendAs(global, chatId));
});

addActionHandler('loadTopInlineBots', async (global) => {
  const { lastRequestedAt } = global.topInlineBots;
  if (lastRequestedAt && getServerTime(global.serverTimeOffset) - lastRequestedAt < TOP_PEERS_REQUEST_COOLDOWN) {
    return;
  }

  const result = await callApi('fetchTopInlineBots');
  if (!result) {
    return;
  }

  const { ids, users } = result;

  global = getGlobal();
  global = addUsers(global, buildCollectionByKey(users, 'id'));
  global = {
    ...global,
    topInlineBots: {
      ...global.topInlineBots,
      userIds: ids,
      lastRequestedAt: getServerTime(global.serverTimeOffset),
    },
  };
  setGlobal(global);
});

addActionHandler('queryInlineBot', async (global, actions, payload) => {
  const {
    chatId, username, query, offset,
  } = payload;

  let inlineBotData = global.inlineBots.byUsername[username];
  if (inlineBotData === false) {
    return;
  }

  if (inlineBotData === undefined) {
    const { user: inlineBot, chat } = await callApi('fetchInlineBot', { username }) || {};
    global = getGlobal();
    if (!inlineBot || !chat) {
      setGlobal(replaceInlineBotSettings(global, username, false));
      return;
    }

    global = addUsers(global, { [inlineBot.id]: inlineBot });
    global = addChats(global, { [chat.id]: chat });
    inlineBotData = {
      id: inlineBot.id,
      query: '',
      offset: '',
      switchPm: undefined,
      canLoadMore: true,
      results: [],
    };

    global = replaceInlineBotSettings(global, username, inlineBotData);
    setGlobal(global);
  }

  if (query === inlineBotData.query && !inlineBotData.canLoadMore) {
    return;
  }

  void runDebouncedForSearch(() => {
    searchInlineBot({
      username,
      inlineBotData: inlineBotData as InlineBotSettings,
      chatId,
      query,
      offset,
    });
  });
});

addActionHandler('switchBotInline', (global, actions, payload) => {
  const { query, isSamePeer, messageId } = payload;
  const chat = selectCurrentChat(global);
  if (!chat) {
    return undefined;
  }
  const message = selectChatMessage(global, chat.id, messageId);
  if (!message) {
    return undefined;
  }

  const botSender = selectChatBot(global, message.senderId!);
  if (!botSender) {
    return undefined;
  }

  const text = `@${botSender.username} ${query}`;

  if (isSamePeer) {
    actions.openChatWithText({ chatId: chat.id, text });
    return undefined;
  }

  return {
    ...global,
    switchBotInline: {
      query,
      botUsername: botSender.username,
    },
  };
});

addActionHandler('resetSwitchBotInline', (global) => {
  return {
    ...global,
    switchBotInline: undefined,
  };
});

addActionHandler('sendInlineBotResult', (global, actions, payload) => {
  const {
    id, queryId, isSilent, scheduledAt,
  } = payload;
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList || !id) {
    return;
  }

  const { chatId, threadId } = currentMessageList;

  const chat = selectChat(global, chatId)!;

  actions.setReplyingToId({ messageId: undefined });
  actions.clearWebPagePreview({ chatId, threadId, value: false });

  void callApi('sendInlineBotResult', {
    chat,
    resultId: id,
    queryId,
    replyingTo: selectReplyingToId(global, chatId, threadId),
    sendAs: selectSendAs(global, chatId),
    isSilent,
    scheduleDate: scheduledAt,
  });
});

addActionHandler('resetInlineBot', (global, actions, payload) => {
  const { username } = payload;

  let inlineBotData = global.inlineBots.byUsername[username];

  if (!inlineBotData) {
    return;
  }

  inlineBotData = {
    id: inlineBotData.id,
    query: '',
    offset: '',
    switchPm: undefined,
    canLoadMore: true,
    results: [],
  };

  setGlobal(replaceInlineBotSettings(global, username, inlineBotData));
});

addActionHandler('startBot', async (global, actions, payload) => {
  const { botId, param } = payload;

  const bot = selectUser(global, botId);
  if (!bot) {
    return;
  }

  await callApi('startBot', {
    bot,
    startParam: param,
  });
});

addActionHandler('requestSimpleWebView', async (global, actions, payload) => {
  const {
    url, bot, theme, buttonText,
  } = payload;

  if (!selectIsTrustedBot(global, bot)) {
    setGlobal({
      ...global,
      botTrustRequest: {
        bot,
        type: 'webApp',
        onConfirm: {
          action: 'requestSimpleWebView',
          payload,
        },
      },
    });
    return;
  }

  const webViewUrl = await callApi('requestSimpleWebView', { url, bot, theme });
  if (!webViewUrl) {
    return;
  }

  global = getGlobal();
  setGlobal({
    ...global,
    webApp: {
      url: webViewUrl,
      bot,
      buttonText,
    },
  });
});

addActionHandler('requestWebView', async (global, actions, payload) => {
  const {
    url, bot, peer, theme, isSilent, buttonText, isFromBotMenu, startParam,
  } = payload;

  if (!selectIsTrustedBot(global, bot)) {
    setGlobal({
      ...global,
      botTrustRequest: {
        bot,
        type: 'webApp',
        onConfirm: {
          action: 'requestWebView',
          payload,
        },
      },
    });
    return;
  }

  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return;
  }

  const { chatId, threadId } = currentMessageList;
  const reply = chatId && selectReplyingToId(global, chatId, threadId);
  const result = await callApi('requestWebView', {
    url,
    bot,
    peer,
    theme,
    isSilent,
    replyToMessageId: reply || undefined,
    isFromBotMenu,
    startParam,
  });
  if (!result) {
    return;
  }

  const { url: webViewUrl, queryId } = result;

  global = getGlobal();
  setGlobal({
    ...global,
    webApp: {
      url: webViewUrl,
      bot,
      queryId,
      buttonText,
    },
  });
});

addActionHandler('prolongWebView', async (global, actions, payload) => {
  const {
    bot, peer, isSilent, replyToMessageId, queryId,
  } = payload;

  const result = await callApi('prolongWebView', {
    bot,
    peer,
    isSilent,
    replyToMessageId,
    queryId,
  });

  if (!result) {
    actions.closeWebApp();
  }
});

addActionHandler('sendWebViewData', (global, actions, payload) => {
  const {
    bot, data, buttonText,
  } = payload;

  callApi('sendWebViewData', {
    bot,
    data,
    buttonText,
  });
});

addActionHandler('closeWebApp', (global) => {
  return {
    ...global,
    webApp: undefined,
  };
});

addActionHandler('cancelBotTrustRequest', (global) => {
  return {
    ...global,
    botTrustRequest: undefined,
  };
});

addActionHandler('markBotTrusted', (global, actions, payload) => {
  const { botId } = payload;
  const { trustedBotIds } = global;

  const newTrustedBotIds = new Set(trustedBotIds);
  newTrustedBotIds.add(botId);
  setGlobal({
    ...global,
    botTrustRequest: undefined,
    trustedBotIds: Array.from(newTrustedBotIds),
  });

  if (global.botTrustRequest?.onConfirm) {
    const { action, payload: callbackPayload } = global.botTrustRequest.onConfirm;
    actions[action](callbackPayload);
  }
});

addActionHandler('loadAttachMenuBots', async (global, actions, payload) => {
  const { hash } = payload || {};
  await loadAttachMenuBots(hash);
});

addActionHandler('toggleBotInAttachMenu', async (global, actions, payload) => {
  const { botId, isEnabled } = payload;

  const bot = selectUser(global, botId);

  if (!bot) return;

  await toggleBotInAttachMenu(bot, isEnabled);
});

async function toggleBotInAttachMenu(bot: ApiUser, isEnabled: boolean) {
  await callApi('toggleBotInAttachMenu', { bot, isEnabled });
  await loadAttachMenuBots();
}

async function loadAttachMenuBots(hash?: string) {
  const result = await callApi('loadAttachMenuBots', { hash });
  if (!result) {
    return;
  }

  const global = getGlobal();
  setGlobal({
    ...global,
    attachMenu: {
      hash: result.hash,
      bots: result.bots,
    },
  });
}

addActionHandler('callAttachMenuBot', (global, actions, payload) => {
  const {
    chatId, botId, isFromBotMenu, url, startParam,
  } = payload;
  const chat = selectChat(global, chatId);
  const bot = selectChatBot(global, botId);
  if (!chat || !bot) {
    return undefined;
  }
  const { attachMenu: { bots } } = global;
  if (!isFromBotMenu && !bots[botId]) {
    return {
      ...global,
      botAttachRequest: {
        bot,
        chatId,
        startParam,
      },
    };
  }
  const theme = extractCurrentThemeParams();
  actions.requestWebView({
    url,
    peer: chat,
    bot,
    theme,
    buttonText: '',
    isFromBotMenu,
    startParam,
  });

  return undefined;
});

addActionHandler('confirmBotAttachRequest', async (global, actions) => {
  const { botAttachRequest } = global;
  if (!botAttachRequest) return;

  const { bot, chatId, startParam } = botAttachRequest;

  setGlobal({
    ...global,
    botAttachRequest: undefined,
  });

  await toggleBotInAttachMenu(bot, true);

  actions.callAttachMenuBot({ chatId, botId: bot.id, startParam });
});

addActionHandler('closeBotAttachRequestModal', (global) => {
  return {
    ...global,
    botAttachRequest: undefined,
  };
});

async function searchInlineBot({
  username,
  inlineBotData,
  chatId,
  query,
  offset,
}: {
  username: string;
  inlineBotData: InlineBotSettings;
  chatId: string;
  query: string;
  offset?: string;
}) {
  let global = getGlobal();
  const bot = selectUser(global, inlineBotData.id);
  const chat = selectChat(global, chatId);
  if (!bot || !chat) {
    return;
  }

  const shouldReplaceSettings = inlineBotData.query !== query;
  global = replaceInlineBotsIsLoading(global, true);
  global = replaceInlineBotSettings(global, username, {
    ...inlineBotData,
    query,
    ...(shouldReplaceSettings && { offset: undefined, results: [] }),
  });
  setGlobal(global);

  const result = await callApi('fetchInlineBotResults', {
    bot,
    chat,
    query,
    offset: shouldReplaceSettings ? undefined : offset,
  });

  const newInlineBotData = global.inlineBots.byUsername[username];
  global = replaceInlineBotsIsLoading(getGlobal(), false);
  if (!result || !newInlineBotData || query !== newInlineBotData.query) {
    setGlobal(global);
    return;
  }

  const currentIds = new Set((newInlineBotData.results || []).map((data) => data.id));
  const newResults = result.results.filter((data) => !currentIds.has(data.id));

  global = replaceInlineBotSettings(global, username, {
    ...newInlineBotData,
    help: result.help,
    ...(newResults.length && { isGallery: result.isGallery }),
    ...(result.switchPm && { switchPm: result.switchPm }),
    canLoadMore: result.results.length > 0 && Boolean(result.nextOffset),
    results: newInlineBotData.offset === '' || newInlineBotData.offset === result.nextOffset
      ? result.results
      : (newInlineBotData.results || []).concat(newResults),
    offset: newResults.length ? result.nextOffset : '',
  });

  setGlobal(global);
}

async function sendBotCommand(
  chat: ApiChat, currentUserId: string, command: string, replyingTo?: number, sendAs?: ApiChat | ApiUser,
) {
  await callApi('sendMessage', {
    chat,
    text: command,
    replyingTo,
    sendAs,
  });
}

let gameePopups: PopupManager | undefined;

async function answerCallbackButton(chat: ApiChat, messageId: number, data?: string, isGame = false) {
  const {
    showDialog, showNotification, toggleSafeLinkModal, openGame,
  } = getActions();

  if (isGame) {
    if (!gameePopups) {
      gameePopups = new PopupManager('popup,width=800,height=600', () => {
        showNotification({ message: 'Allow browser to open popup window' });
      });
    }

    gameePopups.preOpenIfNeeded();
  }

  const result = await callApi('answerCallbackButton', {
    chatId: chat.id,
    accessHash: chat.accessHash,
    messageId,
    data,
    isGame,
  });

  if (!result) {
    return;
  }
  const { message, alert: isError, url } = result;

  if (isError) {
    showDialog({ data: { message: message || 'Error' } });
  } else if (message) {
    showNotification({ message });
  } else if (url) {
    if (isGame) {
      // Workaround for Gamee embedding bug
      if (url.includes(GAMEE_URL)) {
        gameePopups!.open(url);
      } else {
        gameePopups!.cancelPreOpen();
        openGame({ url, chatId: chat.id, messageId });
      }
    } else {
      toggleSafeLinkModal({ url });
    }
  }
}
