import { reactive } from 'vue'
import { find, omitBy, orderBy, sumBy } from 'lodash'
import chatService from '../services/chat_service/chat_service.js'
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../services/promise_interval/promise_interval.js'

const emptyChatList = () => ({
  data: [],
  idStore: {}
})

const defaultState = {
  chatList: emptyChatList(),
  chatListFetcher: null,
  openedChats: reactive({}),
  openedChatMessageServices: reactive({}),
  fetcher: undefined,
  currentChatId: null,
  lastReadMessageId: null
}

const getChatById = (state, id) => {
  return find(state.chatList.data, { id })
}

const sortedChatList = (state) => {
  return orderBy(state.chatList.data, ['updated_at'], ['desc'])
}

const unreadChatCount = (state) => {
  return sumBy(state.chatList.data, 'unread')
}

const chats = {
  state: { ...defaultState },
  getters: {
    currentChat: state => state.openedChats[state.currentChatId],
    currentChatMessageService: state => state.openedChatMessageServices[state.currentChatId],
    findOpenedChatByRecipientId: state => recipientId => find(state.openedChats, c => c.account.id === recipientId),
    sortedChatList,
    unreadChatCount
  },
  actions: {
    // Chat list
    startFetchingChats ({ dispatch, commit }) {
      const fetcher = () => dispatch('fetchChats', { latest: true })
      fetcher()
      commit('setChatListFetcher', {
        fetcher: () => promiseInterval(fetcher, 5000)
      })
    },
    stopFetchingChats ({ commit }) {
      commit('setChatListFetcher', { fetcher: undefined })
    },
    fetchChats ({ dispatch, rootState, commit }, params = {}) {
      return rootState.api.backendInteractor.chats()
        .then(({ chats }) => {
          dispatch('addNewChats', { chats })
          return chats
        })
    },
    addNewChats (store, { chats }) {
      const { commit, dispatch, rootGetters } = store
      const newChatMessageSideEffects = (chat) => {
        maybeShowChatNotification(store, chat)
      }
      commit('addNewChats', { dispatch, chats, rootGetters, newChatMessageSideEffects })
    },
    updateChat ({ commit }, { chat }) {
      commit('updateChat', { chat })
    },

    // Opened Chats
    startFetchingCurrentChat ({ commit, dispatch }, { fetcher }) {
      dispatch('setCurrentChatFetcher', { fetcher })
    },
    setCurrentChatFetcher ({ rootState, commit }, { fetcher }) {
      commit('setCurrentChatFetcher', { fetcher })
    },
    addOpenedChat ({ rootState, commit, dispatch }, { chat }) {
      commit('addOpenedChat', { dispatch, chat: parseChat(chat) })
      dispatch('addNewUsers', [chat.account])
    },
    addChatMessages ({ commit }, value) {
      commit('addChatMessages', { commit, ...value })
    },
    resetChatNewMessageCount ({ commit }, value) {
      commit('resetChatNewMessageCount', value)
    },
    clearCurrentChat ({ rootState, commit, dispatch }, value) {
      commit('setCurrentChatId', { chatId: undefined })
      commit('setCurrentChatFetcher', { fetcher: undefined })
    },
    readChat ({ rootState, commit, dispatch }, { id, lastReadId }) {
      const isNewMessage = rootState.chats.lastReadMessageId !== lastReadId

      dispatch('resetChatNewMessageCount')
      commit('readChat', { id, lastReadId })

      if (isNewMessage) {
        rootState.api.backendInteractor.readChat({ id, lastReadId })
      }
    },
    deleteChatMessage ({ rootState, commit }, value) {
      rootState.api.backendInteractor.deleteChatMessage(value)
      commit('deleteChatMessage', { commit, ...value })
    },
    resetChats ({ commit, dispatch }) {
      dispatch('clearCurrentChat')
      commit('resetChats', { commit })
    },
    clearOpenedChats ({ rootState, commit, dispatch, rootGetters }) {
      commit('clearOpenedChats', { commit })
    },
    handleMessageError ({ commit }, value) {
      commit('handleMessageError', { commit, ...value })
    },
    cullOlderMessages ({ commit }, chatId) {
      commit('cullOlderMessages', chatId)
    }
  },
  mutations: {
    setChatListFetcher (state, { commit, fetcher }) {
      const prevFetcher = state.chatListFetcher
      if (prevFetcher) {
        prevFetcher.stop()
      }
      state.chatListFetcher = fetcher && fetcher()
    },
    setCurrentChatFetcher (state, { fetcher }) {
      const prevFetcher = state.fetcher
      if (prevFetcher) {
        prevFetcher.stop()
      }
      state.fetcher = fetcher && fetcher()
    },
    addOpenedChat (state, { _dispatch, chat }) {
      state.currentChatId = chat.id
      state.openedChats[chat.id] = chat

      if (!state.openedChatMessageServices[chat.id]) {
        state.openedChatMessageServices[chat.id] = chatService.empty(chat.id)
      }
    },
    setCurrentChatId (state, { chatId }) {
      state.currentChatId = chatId
    },
    addNewChats (state, { chats, newChatMessageSideEffects }) {
      chats.forEach((updatedChat) => {
        const chat = getChatById(state, updatedChat.id)

        if (chat) {
          const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id)
          chat.lastMessage = updatedChat.lastMessage
          chat.unread = updatedChat.unread
          chat.updated_at = updatedChat.updated_at
          if (isNewMessage && chat.unread) {
            newChatMessageSideEffects(updatedChat)
          }
        } else {
          state.chatList.data.push(updatedChat)
          state.chatList.idStore[updatedChat.id] = updatedChat
        }
      })
    },
    updateChat (state, { _dispatch, chat: updatedChat, _rootGetters }) {
      const chat = getChatById(state, updatedChat.id)
      if (chat) {
        chat.lastMessage = updatedChat.lastMessage
        chat.unread = updatedChat.unread
        chat.updated_at = updatedChat.updated_at
      }
      if (!chat) { state.chatList.data.unshift(updatedChat) }
      state.chatList.idStore[updatedChat.id] = updatedChat
    },
    deleteChat (state, { _dispatch, id, _rootGetters }) {
      state.chats.data = state.chats.data.filter(conversation =>
        conversation.last_status.id !== id
      )
      state.chats.idStore = omitBy(state.chats.idStore, conversation => conversation.last_status.id === id)
    },
    resetChats (state, { commit }) {
      state.chatList = emptyChatList()
      state.currentChatId = null
      commit('setChatListFetcher', { fetcher: undefined })
      for (const chatId in state.openedChats) {
        chatService.clear(state.openedChatMessageServices[chatId])
        delete state.openedChats[chatId]
        delete state.openedChatMessageServices[chatId]
      }
    },
    setChatsLoading (state, { value }) {
      state.chats.loading = value
    },
    addChatMessages (state, { chatId, messages, updateMaxId }) {
      const chatMessageService = state.openedChatMessageServices[chatId]
      if (chatMessageService) {
        chatService.add(chatMessageService, { messages: messages.map(parseChatMessage), updateMaxId })
      }
    },
    deleteChatMessage (state, { chatId, messageId }) {
      const chatMessageService = state.openedChatMessageServices[chatId]
      if (chatMessageService) {
        chatService.deleteMessage(chatMessageService, messageId)
      }
    },
    resetChatNewMessageCount (state, _value) {
      const chatMessageService = state.openedChatMessageServices[state.currentChatId]
      chatService.resetNewMessageCount(chatMessageService)
    },
    // Used when a connection loss occurs
    clearOpenedChats (state) {
      const currentChatId = state.currentChatId
      for (const chatId in state.openedChats) {
        if (currentChatId !== chatId) {
          chatService.clear(state.openedChatMessageServices[chatId])
          delete state.openedChats[chatId]
          delete state.openedChatMessageServices[chatId]
        }
      }
    },
    readChat (state, { id, lastReadId }) {
      state.lastReadMessageId = lastReadId
      const chat = getChatById(state, id)
      if (chat) {
        chat.unread = 0
      }
    },
    handleMessageError (state, { chatId, fakeId, isRetry }) {
      const chatMessageService = state.openedChatMessageServices[chatId]
      chatService.handleMessageError(chatMessageService, fakeId, isRetry)
    },
    cullOlderMessages (state, chatId) {
      chatService.cullOlderMessages(state.openedChatMessageServices[chatId])
    }
  }
}

export default chats
