import _ from 'lodash'

const empty = (chatId) => {
  return {
    idIndex: {},
    idempotencyKeyIndex: {},
    messages: [],
    newMessageCount: 0,
    lastSeenMessageId: '0',
    chatId,
    minId: undefined,
    maxId: undefined
  }
}

const clear = (storage) => {
  const failedMessageIds = []

  for (const message of storage.messages) {
    if (message.error) {
      failedMessageIds.push(message.id)
    } else {
      delete storage.idIndex[message.id]
      delete storage.idempotencyKeyIndex[message.idempotency_key]
    }
  }

  storage.messages = storage.messages.filter(m => failedMessageIds.includes(m.id))
  storage.newMessageCount = 0
  storage.lastSeenMessageId = '0'
  storage.minId = undefined
  storage.maxId = undefined
}

const deleteMessage = (storage, messageId) => {
  if (!storage) { return }
  storage.messages = storage.messages.filter(m => m.id !== messageId)
  delete storage.idIndex[messageId]

  if (storage.maxId === messageId) {
    const lastMessage = _.maxBy(storage.messages, 'id')
    storage.maxId = lastMessage.id
  }

  if (storage.minId === messageId) {
    const firstMessage = _.minBy(storage.messages, 'id')
    storage.minId = firstMessage.id
  }
}

const cullOlderMessages = (storage) => {
  const maxIndex = storage.messages.length
  const minIndex = maxIndex - 50
  if (maxIndex <= 50) return

  storage.messages = _.sortBy(storage.messages, ['id'])
  storage.minId = storage.messages[minIndex].id
  for (const message of storage.messages) {
    if (message.id < storage.minId) {
      delete storage.idIndex[message.id]
      delete storage.idempotencyKeyIndex[message.idempotency_key]
    }
  }
  storage.messages = storage.messages.slice(minIndex, maxIndex)
}

const handleMessageError = (storage, fakeId, isRetry) => {
  if (!storage) { return }
  const fakeMessage = storage.idIndex[fakeId]
  if (fakeMessage) {
    fakeMessage.error = true
    fakeMessage.pending = false
    if (!isRetry) {
      // Ensure the failed message doesn't stay at the bottom of the list.
      const lastPersistedMessage = _.orderBy(storage.messages, ['pending', 'id'], ['asc', 'desc'])[0]
      if (lastPersistedMessage) {
        const oldId = fakeMessage.id
        fakeMessage.id = `${lastPersistedMessage.id}-${new Date().getTime()}`
        storage.idIndex[fakeMessage.id] = fakeMessage
        delete storage.idIndex[oldId]
      }
    }
  }
}

const add = (storage, { messages: newMessages, updateMaxId = true }) => {
  if (!storage) { return }
  for (let i = 0; i < newMessages.length; i++) {
    const message = newMessages[i]

    // sanity check
    if (message.chat_id !== storage.chatId) { return }

    if (message.fakeId) {
      const fakeMessage = storage.idIndex[message.fakeId]
      if (fakeMessage) {
        // In case the same id exists (chat update before POST response)
        // make sure to remove the older duplicate message.
        if (storage.idIndex[message.id]) {
          delete storage.idIndex[message.id]
          storage.messages = storage.messages.filter(msg => msg.id !== message.id)
        }
        Object.assign(fakeMessage, message, { error: false })
        delete fakeMessage.fakeId
        storage.idIndex[fakeMessage.id] = fakeMessage
        delete storage.idIndex[message.fakeId]

        return
      }
    }

    if (!storage.minId || (!message.pending && message.id < storage.minId)) {
      storage.minId = message.id
    }

    if (!storage.maxId || message.id > storage.maxId) {
      if (updateMaxId) {
        storage.maxId = message.id
      }
    }

    if (!storage.idIndex[message.id] && !isConfirmation(storage, message)) {
      if (storage.lastSeenMessageId < message.id) {
        storage.newMessageCount++
      }
      storage.idIndex[message.id] = message
      storage.messages.push(storage.idIndex[message.id])
      storage.idempotencyKeyIndex[message.idempotency_key] = true
    }
  }
}

const isConfirmation = (storage, message) => {
  if (!message.idempotency_key) return
  return storage.idempotencyKeyIndex[message.idempotency_key]
}

const resetNewMessageCount = (storage) => {
  if (!storage) { return }
  storage.newMessageCount = 0
  storage.lastSeenMessageId = storage.maxId
}

// Inserts date separators and marks the head and tail if it's the chain of messages made by the same user
const getView = (storage) => {
  if (!storage) { return [] }

  const result = []
  const messages = _.orderBy(storage.messages, ['pending', 'id'], ['asc', 'asc'])
  const firstMessage = messages[0]
  let previousMessage = messages[messages.length - 1]
  let currentMessageChainId

  if (firstMessage) {
    const date = new Date(firstMessage.created_at)
    date.setHours(0, 0, 0, 0)
    result.push({
      type: 'date',
      date,
      id: date.getTime().toString()
    })
  }

  let afterDate = false

  for (let i = 0; i < messages.length; i++) {
    const message = messages[i]
    const nextMessage = messages[i + 1]

    const date = new Date(message.created_at)
    date.setHours(0, 0, 0, 0)

    // insert date separator and start a new message chain
    if (previousMessage && previousMessage.date < date) {
      result.push({
        type: 'date',
        date,
        id: date.getTime().toString()
      })

      previousMessage.isTail = true
      currentMessageChainId = undefined
      afterDate = true
    }

    const object = {
      type: 'message',
      data: message,
      date,
      id: message.id,
      messageChainId: currentMessageChainId
    }

    // end a message chian
    if ((nextMessage && nextMessage.account_id) !== message.account_id) {
      object.isTail = true
      currentMessageChainId = undefined
    }

    // start a new message chain
    if ((previousMessage && previousMessage.data && previousMessage.data.account_id) !== message.account_id || afterDate) {
      currentMessageChainId = _.uniqueId()
      object.isHead = true
      object.messageChainId = currentMessageChainId
    }

    result.push(object)
    previousMessage = object
    afterDate = false
  }

  return result
}

const ChatService = {
  add,
  empty,
  getView,
  deleteMessage,
  cullOlderMessages,
  resetNewMessageCount,
  clear,
  handleMessageError
}

export default ChatService
