import type { ActionReturnType } from '../../types';

import { DEBUG, MESSAGE_ID_REQUIRED_ERROR } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { oldTranslate } from '../../../util/oldLangProvider';
import { getServerTime } from '../../../util/serverTime';
import { callApi } from '../../../api/gramjs';
import { buildApiInputPrivacyRules } from '../../helpers';
import { addActionHandler, getGlobal, setGlobal } from '../../index';
import {
  addStories,
  addStoriesForPeer,
  removePeerStory,
  updateLastReadStoryForPeer,
  updateLastViewedStoryForPeer,
  updatePeer,
  updatePeerProfileStory,
  updatePeerStoriesFullyLoaded,
  updatePeerStoriesHidden,
  updatePeerStory,
  updatePeerStoryViews,
  updatePeersWithStories,
  updateSentStoryReaction,
  updateStealthMode,
  updateStoryViews,
  updateStoryViewsLoading,
} from '../../reducers';
import { updateTabState } from '../../reducers/tabs';
import {
  selectIsCurrentUserFrozen,
  selectPeer, selectPeerStories, selectPeerStory,
  selectPinnedStories, selectTabState,
} from '../../selectors';
import { selectActiveStoriesCollectionId } from '../../selectors/stories';

const INFINITE_LOOP_MARKER = 100;

addActionHandler('loadAllStories', async (global): Promise<void> => {
  let i = 0;

  while (global.stories.hasNext) {
    if (i++ >= INFINITE_LOOP_MARKER) {
      if (DEBUG) {
        // eslint-disable-next-line no-console
        console.error('`actions/loadAllStories`: Infinite loop detected');
      }

      return;
    }

    global = getGlobal();
    const { stateHash, hasNext } = global.stories;
    if (stateHash && !hasNext) {
      return;
    }

    const result = await callApi('fetchAllStories', {
      isFirstRequest: !stateHash,
      stateHash,
    });

    if (!result) {
      return;
    }

    global = getGlobal();
    global.stories.stateHash = result.state;

    if ('peerStories' in result) {
      global = addStories(global, result.peerStories);
      global = updatePeersWithStories(global, result.peerStories);
      global = updateStealthMode(global, result.stealthMode);
      global.stories.hasNext = result.hasMore;
    }

    setGlobal(global);
  }
});

addActionHandler('loadAllHiddenStories', async (global): Promise<void> => {
  let i = 0;

  while (global.stories.hasNextInArchive) {
    if (i++ >= INFINITE_LOOP_MARKER) {
      if (DEBUG) {
        // eslint-disable-next-line no-console
        console.error('`actions/loadAllHiddenStories`: Infinite loop detected');
      }

      return;
    }

    global = getGlobal();
    const { archiveStateHash, hasNextInArchive } = global.stories;
    if (archiveStateHash && !hasNextInArchive) {
      return;
    }

    const result = await callApi('fetchAllStories', {
      isFirstRequest: !archiveStateHash,
      stateHash: archiveStateHash,
      isHidden: true,
    });

    if (!result) {
      return;
    }

    global = getGlobal();
    global.stories.archiveStateHash = result.state;

    if ('peerStories' in result) {
      global = addStories(global, result.peerStories);
      global = updatePeersWithStories(global, result.peerStories);
      global = updateStealthMode(global, result.stealthMode);
      global.stories.hasNextInArchive = result.hasMore;
    }

    setGlobal(global);
  }
});

addActionHandler('loadPeerSkippedStories', async (global, actions, payload): Promise<void> => {
  const { peerId } = payload;
  const peer = selectPeer(global, peerId);
  const peerStories = selectPeerStories(global, peerId);
  if (!peer || !peerStories) {
    return;
  }
  const skippedStoryIds = Object.values(peerStories.byId).reduce((acc, story) => {
    if (!('content' in story)) {
      acc.push(story.id);
    }

    return acc;
  }, [] as number[]);

  if (skippedStoryIds.length === 0) {
    return;
  }

  const result = await callApi('fetchPeerStoriesByIds', {
    peer,
    ids: skippedStoryIds,
  });

  if (!result) {
    return;
  }

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds);
  setGlobal(global);
});

addActionHandler('viewStory', async (global, actions, payload): Promise<void> => {
  const { peerId, storyId, tabId = getCurrentTabId() } = payload;
  const peer = selectPeer(global, peerId);
  const story = selectPeerStory(global, peerId, storyId);
  if (!peer || !story || !('content' in story)) {
    return;
  }

  global = updateLastViewedStoryForPeer(global, peerId, storyId, tabId);
  setGlobal(global);

  const serverTime = getServerTime();

  if (story.expireDate < serverTime && story.isInProfile) {
    void callApi('viewStory', { peer, storyId });
  }

  const isUnread = (global.stories.byPeerId[peerId].lastReadId || 0) < story.id;
  if (!isUnread) {
    return;
  }

  const result = await callApi('markStoryRead', {
    peer,
    storyId,
  });

  if (!result) {
    return;
  }

  global = getGlobal();
  global = updateLastReadStoryForPeer(global, peerId, storyId);
  setGlobal(global);
});

addActionHandler('deleteStory', async (global, actions, payload): Promise<void> => {
  const { peerId, storyId } = payload;

  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const result = await callApi('deleteStory', { peer, storyId });

  if (!result) {
    return;
  }

  global = getGlobal();
  global = removePeerStory(global, peerId, storyId);
  setGlobal(global);
});

addActionHandler('toggleStoryInProfile', async (global, actions, payload): Promise<void> => {
  const { peerId, storyId, isInProfile } = payload;

  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const story = selectPeerStory(global, peerId, storyId);
  const currentIsPinned = story && 'content' in story ? story.isInProfile : undefined;
  global = updatePeerStory(global, peerId, storyId, { isInProfile });
  global = updatePeerProfileStory(global, peerId, storyId, isInProfile);
  setGlobal(global);

  const result = await callApi('toggleStoryInProfile', { peer, storyId, isInProfile });
  if (!result?.length) {
    global = getGlobal();
    global = updatePeerStory(global, peerId, storyId, { isInProfile: currentIsPinned });
    global = updatePeerProfileStory(global, peerId, storyId, currentIsPinned);
    setGlobal(global);
  }
});

addActionHandler('toggleStoryPinnedToTop', async (global, actions, payload): Promise<void> => {
  const { peerId, storyId } = payload;
  const peer = selectPeer(global, peerId);
  const peerStories = selectPeerStories(global, peerId);
  if (!peer || !peerStories) {
    return;
  }

  const oldPinnedIds = selectPinnedStories(global, peerId)?.map((s) => s.id) || [];
  const isRemoving = oldPinnedIds.includes(storyId);
  const newPinnedIds = isRemoving ? oldPinnedIds.filter((id) => id !== storyId) : [...oldPinnedIds, storyId];

  global = getGlobal();
  global = {
    ...global,
    stories: {
      ...global.stories,
      byPeerId: {
        ...global.stories.byPeerId,
        [peerId]: {
          ...peerStories,
          pinnedIds: newPinnedIds.sort((a, b) => b - a),
        },
      },
    },
  };
  setGlobal(global);
  const result = await callApi('toggleStoryPinnedToTop', { peer, storyIds: newPinnedIds });

  if (!result) {
    global = getGlobal();
    global = {
      ...global,
      stories: {
        ...global.stories,
        byPeerId: {
          ...global.stories.byPeerId,
          [peerId]: {
            ...peerStories,
            pinnedIds: oldPinnedIds,
          },
        },
      },
    };
    setGlobal(global);
  }
});

addActionHandler('loadPeerStories', async (global, actions, payload): Promise<void> => {
  if (selectIsCurrentUserFrozen(global)) return;

  const { peerId } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) return;

  const result = await callApi('fetchPeerStories', { peer });
  if (!result) {
    return;
  }

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories);
  if (result.lastReadStoryId) {
    global = updateLastReadStoryForPeer(global, peerId, result.lastReadStoryId);
  }
  setGlobal(global);
});

addActionHandler('loadPeerProfileStories', async (global, actions, payload): Promise<void> => {
  if (selectIsCurrentUserFrozen(global)) return;

  const { peerId, offsetId, tabId = getCurrentTabId() } = payload;
  const peer = selectPeer(global, peerId);
  let peerStories = selectPeerStories(global, peerId);
  if (!peer || peerStories?.isFullyLoaded) {
    return;
  }

  const selectedAlbumId = selectActiveStoriesCollectionId(global, tabId);
  if (selectedAlbumId !== 'all') {
    let albumData = peerStories?.idsByAlbumId?.[selectedAlbumId];
    if (albumData?.isFullyLoaded) {
      return;
    }

    const result = await callApi('fetchAlbumStories', {
      peer,
      albumId: selectedAlbumId,
      offset: offsetId || 0,
    });
    if (!result) {
      return;
    }

    global = getGlobal();
    global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds, false, selectedAlbumId);
    peerStories = selectPeerStories(global, peerId);

    albumData = peerStories?.idsByAlbumId?.[selectedAlbumId];
    if (Object.values(result.stories).length === 0
      || (albumData?.ids?.length && albumData.ids.length >= result.count)) {
      global = updatePeerStoriesFullyLoaded(global, peerId, true, false, selectedAlbumId);
    }
    setGlobal(global);
    return;
  }

  const result = await callApi('fetchPeerProfileStories', { peer, offsetId });
  if (!result) {
    return;
  }

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds);
  peerStories = selectPeerStories(global, peerId);
  if (Object.values(result.stories).length === 0
    || (peerStories?.profileIds?.length && peerStories?.profileIds.length >= result.count)) {
    global = updatePeerStoriesFullyLoaded(global, peerId, true);
  }

  setGlobal(global);
});

addActionHandler('loadStoriesArchive', async (global, actions, payload): Promise<void> => {
  if (selectIsCurrentUserFrozen(global)) return;

  const { peerId, offsetId } = payload;
  const peer = selectPeer(global, peerId);
  let peerStories = selectPeerStories(global, peerId);
  if (!peer || peerStories?.isArchiveFullyLoaded) return;

  const result = await callApi('fetchStoriesArchive', { peer, offsetId });
  if (!result) {
    return;
  }

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories, undefined, true);

  peerStories = selectPeerStories(global, peerId);
  if (Object.values(result.stories).length === 0
    || (peerStories?.archiveIds?.length && peerStories?.archiveIds.length >= result.count)) {
    global = updatePeerStoriesFullyLoaded(global, peerId, true, true);
  }
  setGlobal(global);
});

addActionHandler('loadPeerStoriesByIds', async (global, actions, payload): Promise<void> => {
  const { peerId, storyIds } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const result = await callApi('fetchPeerStoriesByIds', { peer, ids: storyIds });
  if (!result) {
    return;
  }

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories);
  setGlobal(global);
});

addActionHandler('loadStoryViews', async (global, actions, payload): Promise<void> => {
  const { peerId, storyId } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const result = await callApi('fetchStoriesViews', { peer, storyIds: [storyId] });

  if (!result) {
    return;
  }

  global = getGlobal();
  global = updatePeerStoryViews(global, peerId, storyId, result.views);
  setGlobal(global);
});

addActionHandler('loadStoryViewList', async (global, actions, payload): Promise<void> => {
  const {
    peerId,
    storyId,
    offset,
    areReactionsFirst,
    areJustContacts,
    query,
    limit,
    tabId = getCurrentTabId(),
  } = payload;

  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  global = updateStoryViewsLoading(global, true, tabId);
  setGlobal(global);

  const result = await callApi('fetchStoryViewList', {
    peer,
    storyId,
    offset,
    areReactionsFirst,
    areJustContacts,
    limit,
    query,
  });
  if (!result) {
    global = getGlobal();
    global = updateStoryViewsLoading(global, false, tabId);
    setGlobal(global);
    return;
  }

  global = getGlobal();
  global = updateStoryViews(global, storyId, result.views, result.nextOffset, tabId);
  setGlobal(global);
});

addActionHandler('reportStory', async (global, actions, payload): Promise<void> => {
  const {
    peerId,
    storyId,
    description = '',
    option = '',
    tabId = getCurrentTabId(),
  } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const response = await callApi('reportStory', {
    peer,
    storyId,
    description,
    option,
  });

  if (!response) return;

  const { result, error } = response;

  if (error === MESSAGE_ID_REQUIRED_ERROR) {
    actions.showNotification({
      message: oldTranslate('lng_report_please_select_messages'),
      tabId,
    });
    actions.closeReportModal({ tabId });
    return;
  }

  if (!result) return;

  if (result.type === 'reported') {
    actions.showNotification({
      message: result
        ? oldTranslate('ReportPeer.AlertSuccess')
        : 'An error occurred while submitting your report. Please, try again later.',
      tabId,
    });
    actions.closeReportModal({ tabId });
    return;
  }

  if (result.type === 'selectOption') {
    global = getGlobal();
    const oldSections = selectTabState(global, tabId).reportModal?.sections;
    const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
    const newSection = {
      title: result.title,
      options: result.options,
      subtitle: selectedOption?.text,
    };
    global = updateTabState(global, {
      reportModal: {
        messageIds: [storyId],
        subject: 'story',
        peerId,
        description,
        sections: oldSections ? [...oldSections, newSection] : [newSection],
      },
    }, tabId);
    setGlobal(global);
  }

  if (result.type === 'comment') {
    global = getGlobal();
    const oldSections = selectTabState(global, tabId).reportModal?.sections;
    const selectedOption = oldSections?.[oldSections.length - 1]?.options?.find((o) => o.option === option);
    const newSection = {
      isOptional: result.isOptional,
      option: result.option,
      title: selectedOption?.text,
    };
    global = updateTabState(global, {
      reportModal: {
        messageIds: [storyId],
        description,
        peerId,
        subject: 'story',
        sections: oldSections ? [...oldSections, newSection] : [newSection],
      },
    }, tabId);
    setGlobal(global);
  }
});

addActionHandler('editStoryPrivacy', (global, actions, payload): ActionReturnType => {
  const {
    peerId,
    storyId,
    privacy,
  } = payload;

  const peer = selectPeer(global, peerId);
  if (!peer) {
    return;
  }

  const allowedIds = [...privacy.allowUserIds, ...privacy.allowChatIds];
  const blockedIds = [...privacy.blockUserIds, ...privacy.blockChatIds];

  const inputPrivacy = buildApiInputPrivacyRules(global, {
    visibility: privacy.visibility,
    isUnspecified: privacy.isUnspecified,
    allowedIds,
    blockedIds,
    botsPrivacy: 'none',
  });

  void callApi('editStoryPrivacy', {
    peer,
    id: storyId,
    privacy: inputPrivacy,
  });
});

addActionHandler('toggleStoriesHidden', async (global, actions, payload): Promise<void> => {
  const { peerId, isHidden } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) return;

  const result = await callApi('toggleStoriesHidden', { peer, isHidden });
  if (!result) return;

  global = getGlobal();
  global = updatePeerStoriesHidden(global, peerId, isHidden);
  setGlobal(global);
});

addActionHandler('loadStoriesMaxIds', async (global, actions, payload): Promise<void> => {
  const { peerIds } = payload;
  const peers = peerIds.map((peerId) => selectPeer(global, peerId)).filter(Boolean);
  if (!peers.length) return;

  const result = await callApi('fetchStoriesMaxIds', { peers });
  if (!result) return;

  const peerIdsToLoad: string[] = [];

  global = getGlobal();
  result.forEach((maxId, i) => {
    const peer = peers[i];
    global = updatePeer(global, peer.id, {
      maxStoryId: maxId.maxId,
      hasStories: Boolean(maxId.maxId),
    });

    if (maxId.maxId && maxId.maxId > 0) {
      peerIdsToLoad.push(peer.id);
    }
  });
  setGlobal(global);

  peerIdsToLoad?.forEach((peerId) => actions.loadPeerStories({ peerId }));
});

addActionHandler('sendStoryReaction', async (global, actions, payload): Promise<void> => {
  const {
    peerId, storyId, containerId, reaction, shouldAddToRecent, tabId = getCurrentTabId(),
  } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) return;

  const story = selectPeerStory(global, peerId, storyId);
  if (!story || !('content' in story)) return;

  const previousReaction = story.sentReaction;
  global = updateSentStoryReaction(global, peerId, storyId, reaction);
  setGlobal(global);

  if (reaction) {
    actions.startActiveReaction({ containerId, reaction, tabId });
  } else {
    actions.stopActiveReaction({ containerId, tabId });
  }

  const result = await callApi('sendStoryReaction', {
    peer, storyId, reaction, shouldAddToRecent,
  });

  global = getGlobal();
  if (!result) {
    global = updateSentStoryReaction(global, peerId, storyId, previousReaction);
  }
  setGlobal(global);
});

addActionHandler('activateStealthMode', (global, actions, payload): ActionReturnType => {
  const { isForPast = true, isForFuture = true } = payload || {};

  callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
});

addActionHandler('loadStoryAlbums', async (global, actions, payload): Promise<void> => {
  const { peerId } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) return;

  const albums = await callApi('fetchAlbums', { peer });
  if (!albums) return;

  global = getGlobal();
  global = {
    ...global,
    stories: {
      ...global.stories,
      albumsByPeerId: {
        ...global.stories.albumsByPeerId,
        [peerId]: albums,
      },
    },
  };
  setGlobal(global);
});

addActionHandler('selectStoryAlbum', (global, actions, payload): ActionReturnType => {
  const { peerId, albumId, tabId = getCurrentTabId() } = payload;

  if (albumId && peerId) {
    global = updatePeerStoriesFullyLoaded(global, peerId, false);
  }

  global = updateTabState(global, {
    selectedStoryAlbumId: albumId || undefined,
  }, tabId);

  setGlobal(global);

  actions.loadPeerProfileStories({ peerId, tabId });
});

addActionHandler('loadAlbumStories', async (global, actions, payload): Promise<void> => {
  const { peerId, albumId, offsetId } = payload;
  const peer = selectPeer(global, peerId);
  if (!peer) return;

  const result = await callApi('fetchAlbumStories', {
    peer,
    albumId,
    offset: offsetId || 0,
  });
  if (!result) return;

  global = getGlobal();
  global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds);
  setGlobal(global);
});

addActionHandler('resetSelectedStoryAlbum', (global, actions, payload): ActionReturnType => {
  const { tabId = getCurrentTabId() } = payload || {};

  return updateTabState(global, {
    selectedStoryAlbumId: undefined,
  }, tabId);
});
