/**
 *   Wechaty - https://github.com/chatie/wechaty
 *
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
import {
  log,
}               from './config.js'

import type { PuppetWeChat }    from './puppet-wechat.js'
import type {
  // WebRecomendInfo,
  WebMessageRawPayload,
}                             from './web-schemas.js'

// import {
//   // FriendRequestPayload,
//   FriendRequestType,
//   FriendRequestPayloadReceive,
//   FriendRequestPayloadConfirm,
// }                               from 'wechaty-puppet'

const REGEX_CONFIG = {
  friendConfirm: [
    /^You have added (.+) as your WeChat contact. Start chatting!$/,
    /^你已添加了(.+)，现在可以开始聊天了。$/,
    /^(.+) just added you to his\/her contacts list. Send a message to him\/her now!$/,
    /^(.+)刚刚把你添加到通讯录，现在可以开始聊天了。$/,
  ],

  roomJoinInvite: [
    // There are 3 blank(charCode is 32) here. eg: You invited 管理员 to the group chat.
    /^(.+?) invited (.+) to the group chat.\s+$/,

    // There no no blank or punctuation here.  eg: 管理员 invited 小桔建群助手 to the group chat
    /^(.+?) invited (.+) to the group chat$/,

    // There are 2 blank(charCode is 32) here. eg: 你邀请"管理员"加入了群聊
    /^(.+?)邀请"(.+)"加入了群聊\s+$/,

    // There no no blank or punctuation here.  eg: "管理员"邀请"宁锐锋"加入了群聊
    /^"(.+?)"邀请"(.+)"加入了群聊$/,
  ],

  roomJoinQrcode: [
    // Wechat change this, should desperate. See more in pr#651
    // /^" (.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,

    // There are 2 blank(charCode is 32) here. Qrcode is shared by bot.
    // eg: "管理员" joined group chat via the QR code you shared.
    /^"(.+)" joined group chat via the QR code "?(.+?)"? shared.\s+$/,

    // There are no blank(charCode is 32) here. Qrcode isn't shared by bot.
    // eg: "宁锐锋" joined the group chat via the QR Code shared by "管理员".
    /^"(.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,

    // There are 2 blank(charCode is 32) here. Qrcode is shared by bot.     eg: "管理员"通过扫描你分享的二维码加入群聊
    /^"(.+)"通过扫描(.+?)分享的二维码加入群聊\s+$/,

    // There are 1 blank(charCode is 32) here. Qrode isn't shared by bot.  eg: " 苏轼"通过扫描"管理员"分享的二维码加入群聊
    /^"\s+(.+)"通过扫描"(.+?)"分享的二维码加入群聊$/,
  ],

  // no list
  roomLeaveIKickOther: [
    /^(You) removed "(.+)" from the group chat$/,
    /^(你)将"(.+)"移出了群聊$/,
  ],

  roomLeaveOtherKickMe: [
    /^(You) were removed from the group chat by "(.+)"$/,
    /^(你)被"(.+)"移出群聊$/,
  ],

  roomTopic: [
    /^"?(.+?)"? changed the group name to "(.+)"$/,
    /^"?(.+?)"?修改群名为“(.+)”$/,
  ],
}

export class Firer {

  constructor (
    public puppet: PuppetWeChat,
  ) {
    //
  }

  // public async checkFriendRequest(
  //   rawPayload : WebMessageRawPayload,
  // ): Promise<void> {
  //   if (!rawPayload.RecommendInfo) {
  //     throw new Error('no RecommendInfo')
  //   }
  //   const recommendInfo: WebRecomendInfo = rawPayload.RecommendInfo
  //   log.verbose('PuppetWeChatFirer', 'fireFriendRequest(%s)', recommendInfo)

  //   if (!recommendInfo) {
  //     throw new Error('no recommendInfo')
  //   }

  //   const contactId = recommendInfo.UserName
  //   const hello     = recommendInfo.Content
  //   const ticket    = recommendInfo.Ticket
  //   const type      = FriendRequestType.Receive
  //   const id        = cuid()

  //   const payloadReceive: FriendRequestPayloadReceive = {
  //     id,
  //     contactId,
  //     hello,
  //     ticket,
  //     type,
  //   }

  //   this.puppet.cacheFriendRequestPayload.set(id, payloadReceive)

  //   this.puppet.emit('friend', id)
  // }

  public async checkFriendConfirm (
    rawPayload : WebMessageRawPayload,
  ) {
    const content = rawPayload.Content
    log.silly('PuppetWeChatFirer', 'fireFriendConfirm(%s)', content)

    if (!this.parseFriendConfirm(content)) {
      return
    }

    // const contactId = rawPayload.FromUserName
    // const type = FriendRequestType.Confirm

    // const id = cuid()

    // const payloadConfirm: FriendRequestPayloadConfirm = {
    //   id,
    //   contactId,
    //   type,
    // }

    // this.puppet.cacheFriendRequestPayload.set(id, payloadConfirm)

    this.puppet.emit('friendship', { friendshipId: rawPayload.MsgId })
  }

  public async checkRoomJoin (
    rawPayload : WebMessageRawPayload,
  ): Promise<boolean> {

    const text   = rawPayload.Content
    const roomId = rawPayload.FromUserName

    /**
     * Get the display names of invitee & inviter
     */
    let inviteeNameList : string[]
    let inviterName     : string

    try {
      [inviteeNameList, inviterName] = this.parseRoomJoin(text)
    } catch (e) {
      log.silly('PuppetWeChatFirer', 'checkRoomJoin() "%s" is not a join message', text)
      return false // not a room join message
    }
    log.silly('PuppetWeChatFirer', 'checkRoomJoin() inviteeList: %s, inviter: %s',
      inviteeNameList.join(','),
      inviterName,
    )

    /**
     * Convert the display name to Contact ID
     */
    let   inviterContactId: undefined | string
    const inviteeContactIdList: string[] = []

    if (/^You|你$/i.test(inviterName)) { //  === 'You' || inviter === '你' || inviter === 'you'
      inviterContactId = this.puppet.currentUserId
    }

    const sleep   = 1000
    const timeout = 60 * 1000
    let   ttl     = timeout / sleep

    let ready = true

    while (ttl-- > 0) {
      log.silly('PuppetWeChatFirer', 'fireRoomJoin() retry() ttl %d', ttl)

      if (!ready) {
        await new Promise(resolve => setTimeout(resolve, timeout))
        ready = true
      }

      /**
       * loop inviteeNameList
       * set inviteeContactIdList
       */
      for (let i = 0; i < inviteeNameList.length; i++) {
        const inviteeName = inviteeNameList[i]!

        const inviteeContactId = inviteeContactIdList[i]
        if (inviteeContactId) {
          /**
           * had already got ContactId for Room Member
           * try to resolve the ContactPayload
           */
          try {
            await this.puppet.contactPayload(inviteeContactId)
          } catch (e) {
            log.warn('PuppetWeChatFirer', 'fireRoomJoin() contactPayload(%s) exception: %s',
              inviteeContactId,
              (e as Error).message,
            )
            ready = false
          }
        } else {
          /**
           * only had Name of RoomMember
           * try to resolve the ContactId & ContactPayload
           */
          const memberIdList = await this.puppet.roomMemberSearch(roomId, inviteeName)

          if (memberIdList.length <= 0) {
            ready = false
          }

          const contactId = memberIdList[0]
          // XXX: Take out the first one if we have matched many contact.
          inviteeContactIdList[i] = contactId!

          if (!contactId) {
            ready = false
          } else {
            try {
              await this.puppet.contactPayload(contactId)
            } catch (e) {
              ready = false
            }
          }

        }

      }

      if (!inviterContactId) {
        const contactIdList = await this.puppet.roomMemberSearch(roomId, inviterName)

        if (contactIdList.length > 0) {
          inviterContactId = contactIdList[0]
        } else {
          ready = false
        }
      }

      if (ready) {
        log.silly('PuppetWeChatFirer', 'fireRoomJoin() resolve() inviteeContactIdList: %s, inviterContactId: %s',
          inviteeContactIdList.join(','),
          inviterContactId,
        )
        /**
         * Resolve All Payload again to make sure the data is ready.
         */
        await Promise.all(
          inviteeContactIdList
            .filter(id => !!id)
            .map(
              id => this.puppet.contactPayload(id!),
            ),
        )

        if (!inviterContactId) {
          throw new Error('no inviterContactId')
        }

        await this.puppet.contactPayload(inviterContactId)
        await this.puppet.roomPayload(roomId)

        const timestamp = Math.floor(Date.now() / 1000) // in seconds
        this.puppet.emit('room-join', {
          inviteeIdList : inviteeContactIdList,
          inviterId     : inviterContactId,
          roomId,
          timestamp,
        })

        return true
      }
    }

    log.warn('PuppetWeChatFier', 'fireRoomJoin() resolve payload fail.')
    return false
  }

  /**
   * You removed "Bruce LEE" from the group chat
   */
  public async checkRoomLeave (
    rawPayload : WebMessageRawPayload,
  ): Promise<boolean> {
    log.verbose('PuppetWeChatFirer', 'fireRoomLeave(%s)', rawPayload.Content)

    const roomId = rawPayload.FromUserName

    let leaverName  : string
    let removerName : string

    try {
      [leaverName, removerName] = this.parseRoomLeave(rawPayload.Content)
    } catch (e) {
      log.silly('PuppetWeChatFirer', 'fireRoomLeave() %s', (e as Error).message)
      return false
    }
    log.silly('PuppetWeChatFirer', 'fireRoomLeave() got leaverName: %s', leaverName)

    /**
     * FIXME: leaver maybe is a list
     * @lijiarui: I have checked, leaver will never be a list.
     * If the bot remove 2 leavers at the same time,
     * it will be 2 sys message, instead of 1 sys message contains 2 leavers.
     */
    let leaverContactId  : undefined | string
    let removerContactId : undefined | string

    if (/^(You|你)$/i.test(leaverName)) {
      leaverContactId = this.puppet.currentUserId
    } else if (/^(You|你)$/i.test(removerName)) {
      removerContactId = this.puppet.currentUserId
    }

    if (!leaverContactId) {
      const idList = await this.puppet.roomMemberSearch(roomId, leaverName)
      leaverContactId = idList[0]
    }

    if (!removerContactId) {
      const idList = await this.puppet.roomMemberSearch(roomId, removerName)
      removerContactId = idList[0]
    }

    if (!leaverContactId || !removerContactId) {
      throw new Error('no id')
    }
    /**
     * FIXME: leaver maybe is a list
     * @lijiarui 2017: I have checked, leaver will never be a list. If the bot remove 2 leavers at the same time,
     *                  it will be 2 sys message, instead of 1 sys message contains 2 leavers.
     * @huan 2018 May: we need to generilize the pattern for future usage.
     */
    const timestamp = Math.floor(Date.now() / 1000) // in seconds

    this.puppet.emit('room-leave', {
      removeeIdList : [leaverContactId],
      removerId     : removerContactId,
      roomId,
      timestamp,
    })

    setTimeout(() => {
      this.puppet.roomPayloadDirty(roomId)
        .then(() => this.puppet.roomPayload(roomId))
        .catch(console.error)
    }, 10 * 1000) // reload the room data, especially for memberList

    return true
  }

  public async checkRoomTopic (
    rawPayload : WebMessageRawPayload,
  ): Promise<boolean> {
    let topic   : string
    let changer : string

    try {
      [topic, changer] = this.parseRoomTopic(rawPayload.Content)
    } catch (e) { // not found
      return false
    }

    const roomId = rawPayload.FromUserName

    const roomPayload = await this.puppet.roomPayload(roomId)
    const oldTopic = roomPayload.topic

    let changerContactId: undefined | string
    if (/^(You|你)$/.test(changer)) {
      changerContactId = this.puppet.currentUserId
    } else {
      changerContactId = (await this.puppet.roomMemberSearch(roomId, changer))[0]
    }

    if (!changerContactId) {
      log.error('PuppetWeChatFirer', 'fireRoomTopic() changer contact not found for %s', changer)
      return false
    }

    try {
      const timestamp = Math.floor(Date.now() / 1000) // in seconds

      this.puppet.emit('room-topic', {
        changerId : changerContactId,
        newTopic  : topic,
        oldTopic,
        roomId,
        timestamp,
      })
      return true
    } catch (e) {
      log.error('PuppetWeChatFirer', 'fireRoomTopic() co exception: %s', (e as Error).stack)
      return false
    }
  }

  /**
   * try to find FriendRequest Confirmation Message
   */
  private parseFriendConfirm (
    content: string,
  ): boolean {
    const reList = REGEX_CONFIG.friendConfirm

    const found = reList.some(re => re.test(content))
    if (found) {
      return true
    } else {
      return false
    }
  }

  /**
   * try to find 'join' event for Room
   *
   * 1.
   *  You invited 管理员 to the group chat.
   *  You invited 李卓桓.PreAngel、Bruce LEE to the group chat.
   * 2.
   *  管理员 invited 小桔建群助手 to the group chat
   *  管理员 invited 庆次、小桔妹 to the group chat
   */
  private parseRoomJoin (
    content: string,
  ): [string[], string] {
    log.verbose('PuppetWeChatFirer', 'parseRoomJoin(%s)', content)

    const reListInvite = REGEX_CONFIG.roomJoinInvite
    const reListQrcode = REGEX_CONFIG.roomJoinQrcode

    let foundInvite: null | string[] = null
    for (const re of reListInvite) {
      foundInvite = content.match(re)
      if (foundInvite) {
        break
      }
    }
    // reListInvite.some(re => !!(foundInvite = content.match(re)))

    let foundQrcode: null | string[] = []
    for (const re of reListQrcode) {
      foundQrcode = content.match(re)
      if (foundQrcode) {
        break
      }
    }
    // reListQrcode.some(re => !!(foundQrcode = content.match(re)))

    if ((!foundInvite || !foundInvite.length) && (!foundQrcode || !foundQrcode.length)) {
      throw new Error('parseRoomJoin() not found matched re of ' + content)
    }
    /**
     * 管理员 invited 庆次、小桔妹 to the group chat
     * "管理员"通过扫描你分享的二维码加入群聊
     */
    const [inviter, inviteeStr] = foundInvite ? [foundInvite[1], foundInvite[2]] : [foundQrcode![2], foundQrcode![1]]

    // FIXME: should also compatible english split
    const inviteeList = inviteeStr?.split(/、/) || []

    return [inviteeList, inviter!] // put invitee at first place
  }

  private parseRoomLeave (
    content: string,
  ): [string, string] {
    let matchIKickOther: null | string[] = null
    for (const re of REGEX_CONFIG.roomLeaveIKickOther) {
      matchIKickOther = content.match(re)
      if (matchIKickOther) {
        break
      }
    }

    let matchOtherKickMe: null | string[] = null
    for (const re of REGEX_CONFIG.roomLeaveOtherKickMe) {
      matchOtherKickMe = content.match(re)
      if (matchOtherKickMe) {
        break
      }
    }

    let leaverName  : undefined | string
    let removerName : undefined | string

    if (matchIKickOther && matchIKickOther.length) {
      leaverName  = matchIKickOther[2]
      removerName = matchIKickOther[1]
    } else if (matchOtherKickMe && matchOtherKickMe.length) {
      leaverName  = matchOtherKickMe[1]
      removerName = matchOtherKickMe[2]
    } else {
      throw new Error('no match')
    }

    return [leaverName!, removerName!]
  }

  private parseRoomTopic (
    content: string,
  ): [string, string] {
    const reList = REGEX_CONFIG.roomTopic

    let found: null | string[] = null
    for (const re of reList) {
      found = content.match(re)
      if (found) {
        break
      }
    }
    if (!found || !found.length) {
      throw new Error('checkRoomTopic() not found')
    }
    const [, changer, topic] = found
    return [topic!, changer!]
  }

}

export default Firer
