import { BadRequest } from '@feathersjs/errors'
import { SequelizeServiceOptions, Service } from 'feathers-sequelize'
import { Op } from 'sequelize'

import { Message as MessageInterface } from '@xrengine/common/src/interfaces/Message'
import { UserInterface } from '@xrengine/common/src/interfaces/User'

import { Application } from '../../../declarations'
import logger from '../../ServerLogger'
import { UserParams } from '../../user/user/user.class'

export interface MessageParams extends UserParams {
  'identity-provider': {
    userId: string
  }
}

export type MessageDataType = MessageInterface

export class Message<T = MessageDataType> extends Service<T> {
  app: Application
  docs: any
  constructor(options: Partial<SequelizeServiceOptions>, app: Application) {
    super(options)
    this.app = app
  }

  /**
   * A function which is used to create a message
   *
   * @param data for new message
   * @param params contain user info
   * @returns {@Object} created message
   */
  async create(data: any, params?: MessageParams): Promise<T> {
    let channel, channelId
    let userIdList: any[] = []
    const loggedInUser = params!.user as UserInterface
    const userId = loggedInUser?.id
    const targetObjectId = data.targetObjectId
    const targetObjectType = data.targetObjectType
    const channelModel = this.app.service('channel').Model
    logger.info(data)

    if (targetObjectType === 'user') {
      const targetUser = await this.app.service('user').get(targetObjectId)
      if (targetUser == null) {
        throw new BadRequest('Invalid target user ID')
      }
      channel = await channelModel.findOne({
        where: {
          [Op.or]: [
            {
              userId1: userId,
              userId2: targetObjectId
            },
            {
              userId2: userId,
              userId1: targetObjectId
            }
          ]
        }
      })
      if (channel == null) {
        channel = await this.app.service('channel').create({
          channelType: 'user',
          userId1: userId,
          userId2: targetObjectId
        })
      }
      channelId = channel.id
      userIdList = [userId, targetObjectId]
    } else if (targetObjectType === 'group') {
      const targetGroup = await this.app.service('group').get(targetObjectId)
      if (targetGroup == null) {
        throw new BadRequest('Invalid target group ID')
      }
      channel = await channelModel.findOne({
        where: {
          groupId: targetObjectId
        }
      })
      if (channel == null) {
        channel = await this.app.service('channel').create({
          channelType: 'group',
          groupId: targetObjectId
        })
      }
      channelId = channel.id
      const groupUsers = await this.app.service('group-user').find({
        query: {
          groupId: targetObjectId
        }
      })
      userIdList = (groupUsers as any).data.map((groupUser) => {
        return groupUser.userId
      })
    } else if (targetObjectType === 'party') {
      const targetParty = await this.app.service('party').Model.count({ where: { id: targetObjectId } })
      if (targetParty <= 0) {
        throw new BadRequest('Invalid target party ID')
      }
      channel = await channelModel.findOne({
        where: {
          partyId: targetObjectId
        }
      })
      if (channel == null) {
        channel = await this.app.service('channel').create({
          channelType: 'party',
          partyId: targetObjectId
        })
      }
      channelId = channel.id
      const partyUsers = await this.app.service('party-user').Model.findAll({ where: { partyId: targetObjectId } })
      userIdList = partyUsers.map((partyUser) => partyUser.userId)
    } else if (targetObjectType === 'instance') {
      const targetInstance = await this.app.service('instance').get(targetObjectId)
      if (targetInstance == null) {
        throw new BadRequest('Invalid target instance ID')
      }
      channel = await channelModel.findOne({
        where: {
          instanceId: targetObjectId
        }
      })
      if (channel == null) {
        channel = await this.app.service('channel').create({
          channelType: 'instance',
          instanceId: targetObjectId
        })
      }
      channelId = channel.id
      const instanceUsers = await this.app.service('user').find({
        query: {
          $limit: 1000,
          instanceId: targetObjectId,
          action: 'layer-users'
        }
      })
      userIdList = (instanceUsers as any).data.map((instanceUser) => {
        return instanceUser.id
      })
    }

    const messageData: any = {
      senderId: userId,
      channelId: channelId,
      text: data.text,
      isNotification: data.isNotification
    }
    const newMessage: any = await super.create({ ...messageData })
    newMessage.sender = loggedInUser

    await Promise.all(
      userIdList.map((mappedUserId: string) => {
        return this.app.service('message-status').create({
          userId: mappedUserId,
          messageId: newMessage.id,
          status: userId === mappedUserId ? 'read' : 'unread'
        })
      })
    )

    await this.app.service('channel').patch(channelId, {
      channelType: channel.channelType
    })

    return newMessage
  }
}
