/* eslint-disable no-redeclare */
import * as PUPPET from '@juzi/wechaty-puppet'
import { FileBox, type FileBoxInterface, log } from '../config.js'
import { WA_ERROR_TYPE } from '../exception/error-type.js'
import WAError from '../exception/whatsapp-error.js'
import { contactRawPayload } from './contact.js'

import type PuppetWhatsApp from '../puppet-whatsapp.js'
import type {
  WhatsAppContactPayload as RoomPayload,
  InviteV4Data,
} from '../schema/whatsapp-type.js'
import { isRoomId } from '../helper/miscellaneous.js'
import { parserRoomRawPayload } from '../helper/pure-function/room-raw-payload-parser.js'
import { getMessageMediaFromFilebox } from '../helper/pure-function/messageMedia.js'

const PRE = 'MIXIN_ROOM'

export async function roomList (this: PuppetWhatsApp): Promise<string[]> {
  log.verbose(PRE, 'roomList()')
  const cacheManager = await this.manager.getCacheManager()
  const roomIdList = await cacheManager.getRoomIdList()
  return roomIdList
}

/**
 * Filter friend list and non-friend list from member id list
 * @param { PuppetWhatsApp } this
 * @param { string[] } memberIdList
 * @returns { friendsList: string[]; nonFriendsList: string[]; }
 */
async function checkRoomMember (this: PuppetWhatsApp, memberIdList: string[]) {
  const friendsList = []
  const nonFriendsList = []
  for (const memberId of memberIdList) {
    const memberPayload = await this.manager.getContactById(memberId)
    if (memberPayload.isMyContact) {
      friendsList.push(memberId)
    } else {
      nonFriendsList.push(memberId)
    }
  }

  const botId = this.manager.whatsAppManager.getBotId()
  if (!friendsList.includes(botId)) {
    friendsList.push(botId)
  }

  return {
    friendsList,
    nonFriendsList,
  }
}

async function updateRoomRawPayloadToCache (
  this: PuppetWhatsApp,
  roomId: string,
  params: {
    name?: string,
    avatar?: string,
    memberIdList?: string[],
  },
): Promise<RoomPayload | undefined> {
  const { name, avatar, memberIdList } = params
  const cacheManager = await this.manager.getCacheManager()
  const roomInCache = await cacheManager.getContactOrRoomRawPayload(roomId)
  if (roomInCache) {
    if (name) {
      roomInCache.name = name
    }
    if (avatar) {
      roomInCache.avatar = avatar
    }
    if (memberIdList && memberIdList.length > 0) {
      await cacheManager.setRoomMemberIdList(roomId, memberIdList)
    }
    await cacheManager.setContactOrRoomRawPayload(roomId, roomInCache)
  }
  return roomInCache
}

export async function roomCreate (
  this: PuppetWhatsApp,
  contactIdList: string[],
  topic: string,
): Promise<string> {
  log.verbose(PRE, 'roomCreate(%s, %s)', contactIdList, topic)
  const { friendsList, nonFriendsList } = await checkRoomMember.call(this, contactIdList)
  const group = await this.manager.createRoom(topic, friendsList)
  const roomId = group.gid._serialized
  if (roomId) {
    if (nonFriendsList.length > 0) {
      await addMemberListToRoom.call(this, roomId, nonFriendsList)
    }
    await updateRoomRawPayloadToCache.call(this, roomId, {
      memberIdList: contactIdList,
      name: topic,
    })
    return roomId
  } else {
    throw WAError(WA_ERROR_TYPE.ERR_CREATE_ROOM, `An error occurred while creating the group, detail: ${contactIdList}, topic: ${topic}`)
  }
}

export async function roomAdd (
  this: PuppetWhatsApp,
  roomId: string,
  contactId: string,
): Promise<void> {
  log.verbose(PRE, 'roomAdd(%s, %s)', roomId, contactId)
  await addMemberListToRoom.call(this, roomId, contactId)
}

async function addMemberListToRoom (
  this: PuppetWhatsApp,
  roomId: string,
  contactIds: string | string[],
) {
  const roomChat = await this.manager.getRoomChatById(roomId)
  const contactIdList = Array.isArray(contactIds) ? contactIds : [contactIds]
  const result = await roomChat.addParticipants(contactIdList)
  const cacheManager = await this.manager.getCacheManager()
  const successContactIdList = []
  if (typeof result === 'string') {
    throw WAError(WA_ERROR_TYPE.ERR_ADD_ROOM, `cannot add contact: ${contactIdList.join(', ')} to room ${roomId}, ${result}`)
  }
  for (const key in result) {
    const item = result[key]
    for (const contactId in item) {
      const contactResult = item[contactId]
      if (Number(contactResult?.code) === 200) {
        log.silly(PRE, `add member ${contactId} to room ${roomId} succeded`)
        successContactIdList.push(contactId)
      } else {
        switch (Number(contactResult?.code)) {
          case 409:
            log.warn(PRE, `add member ${contactId} to room ${roomId} failed, contact is already in room`)
            break
          default:
            log.warn(PRE, `add member ${contactId} to room ${roomId} failed for unknown reason`)
        }
      }
    }
  }
  await cacheManager.addRoomMemberToList(roomId, successContactIdList)

}

export async function roomDel (
  this: PuppetWhatsApp,
  roomId: string,
  contactIds: string | string[],
): Promise<void> {
  log.verbose(PRE, 'roomDel(%s, %s)', roomId, contactIds)
  const roomChat = await this.manager.getRoomChatById(roomId)
  const contactIdList = Array.isArray(contactIds) ? contactIds : [contactIds]
  await roomChat.removeParticipants(contactIdList)
  const cacheManager = await this.manager.getCacheManager()
  await cacheManager.removeRoomMemberFromList(roomId, contactIdList)
}

export async function roomQuit (this: PuppetWhatsApp, roomId: string): Promise<void> {
  log.verbose(PRE, 'roomQuit(%s)', roomId)
  const roomChat = await this.manager.getRoomChatById(roomId)
  await roomChat.leave()
  const cacheManager = await this.manager.getCacheManager()
  await cacheManager.deleteContactOrRoom(roomId)
  await cacheManager.deleteRoomMemberIdList(roomId)
}

export async function roomAvatar (this: PuppetWhatsApp, roomId: string, avatar?: FileBoxInterface): Promise<FileBox | void> {
  log.verbose(PRE, 'roomAvatar(%s)', roomId)

  if (avatar) {
    const media = await getMessageMediaFromFilebox(avatar)
    const roomChat = await this.manager.getRoomChatById(roomId)
    const result = await roomChat.setPicture(media)
    if (!result) {
      throw WAError(WA_ERROR_TYPE.ERR_ROOM_AVATAR_SET_FAILED, `can not set room avatar, room id: ${roomId}`)
    }
  } else {
    const payload = await this.roomPayload(roomId)

    if (payload.avatar) {
      return FileBox.fromUrl(payload.avatar)
    }
    throw WAError(WA_ERROR_TYPE.ERR_ROOM_AVATAR_NOT_FOUND, `can not find this room avatar, room id: ${roomId}`)
  }

}

export async function roomTopic(this: PuppetWhatsApp, roomId: string): Promise<string>
export async function roomTopic(this: PuppetWhatsApp, roomId: string, topic: string): Promise<void>

export async function roomTopic (
  this: PuppetWhatsApp,
  roomId: string,
  topic?: string,
): Promise<void | string> {
  log.verbose(PRE, 'roomTopic(%s, %s)', roomId, topic)

  if (typeof topic === 'undefined') {
    const payload = await this.roomPayload(roomId)
    return payload.topic
  }
  const roomChat = await this.manager.getRoomChatById(roomId)
  if (roomChat.isGroup) {
    await roomChat.setSubject(topic)
  }
  await this.dirtyPayload(PUPPET.types.Payload.Room, roomId)
}

export async function roomQRCode (this: PuppetWhatsApp, roomId: string): Promise<string> {
  log.verbose(PRE, 'roomQRCode(%s)', roomId)
  const roomChat = await this.manager.getRoomChatById(roomId)
  const code = await roomChat.getInviteCode()
  const url = `https://chat.whatsapp.com/${code}`
  return url
}

/**
 * Get member id list from cache
 * @param { PuppetWhatsApp } this whatsapp client
 * @param { string } roomId roomId
 * @returns { string[] } member id list
 */
export async function roomMemberList (this: PuppetWhatsApp, roomId: string): Promise<string[]> {
  log.verbose(PRE, 'roomMemberList(%s)', roomId)
  const cacheManager = await this.manager.getCacheManager()
  const memberList = await cacheManager.getRoomMemberIdList(roomId)
  if (memberList.length === 0) {
    return this.manager.syncRoomMemberList(roomId)
  }
  return memberList
}

export async function roomMemberRawPayload (this: PuppetWhatsApp, roomId: string, contactId: string): Promise<PUPPET.payloads.RoomMember> {
  log.verbose(PRE, 'roomMemberRawPayload(%s, %s)', roomId, contactId)
  const member = await contactRawPayload.call(this, contactId)
  return {
    avatar: member.avatar,
    id: member.id._serialized,
    name: member.pushname || member.name || '',
    // roomAlias : contact.name,
  }
}

export async function roomMemberRawPayloadParser (this: PuppetWhatsApp, rawPayload: PUPPET.payloads.RoomMember): Promise<PUPPET.payloads.RoomMember> {
  log.verbose(PRE, 'roomMemberRawPayloadParser(%s)', JSON.stringify(rawPayload))
  return rawPayload
}

export async function roomAnnounce(this: PuppetWhatsApp, roomId: string): Promise<string>
export async function roomAnnounce(this: PuppetWhatsApp, roomId: string, text: string): Promise<void>

export async function roomAnnounce (this: PuppetWhatsApp, roomId: string, text?: string): Promise<void | string> {
  if (typeof text === 'undefined') {
    const roomChat = await this.manager.getRoomChatById(roomId)
    return roomChat.description
  }
  const roomChat = await this.manager.getRoomChatById(roomId)
  await roomChat.setDescription((text || undefined) as any)
  await this.dirtyPayload(PUPPET.types.Payload.Room, roomId)
}

/**
*
* Room Invitation
*
*/
export async function roomInvitationAccept (this: PuppetWhatsApp, roomInvitationId: string): Promise<void> {
  log.verbose(PRE, 'roomInvitationAccept(%s)', roomInvitationId)

  const info = await roomInvitationRawPayload.call(this, roomInvitationId)

  if (Object.keys(info).length === 1) {
    await this.manager.acceptRoomInvite(info.inviteCode!)
  } else {
    await this.manager.acceptPrivateRoomInvite(info as InviteV4Data)
  }

}

export async function roomInvitationRawPayload (this: PuppetWhatsApp, roomInvitationId: string): Promise<Partial<InviteV4Data>> {
  log.verbose(PRE, 'roomInvitationRawPayload(%s)', roomInvitationId)
  const cacheManager = await this.manager.getCacheManager()
  const info = await cacheManager.getRoomInvitationRawPayload(roomInvitationId)
  if (info) {
    return info
  } else {
    return {
      inviteCode: roomInvitationId,
    }
  }
}

/**
 *
 * @param this PuppetWhatsapp
 * @param rawPayload Partial<InviteV4Data>
 * @returns Partial<InviteV4Data>
 * TODO: Here we return Partial<InviteV4Data> for roomInvitationAccept usage, We may need other fields required by RoomInvitationPayload
 */
export async function roomInvitationRawPayloadParser (this: PuppetWhatsApp, rawPayload: any): Promise<PUPPET.payloads.RoomInvitation> {
  log.verbose(PRE, 'roomInvitationRawPayloadParser(%s)', JSON.stringify(rawPayload))
  return rawPayload
}

export async function roomRawPayload (this: PuppetWhatsApp, id: string): Promise<RoomPayload> {
  log.verbose(PRE, 'roomRawPayload(%s)', id)
  if (!isRoomId(id)) {
    throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `please check room id: ${id} again.`)
  }
  const cacheManager = await this.manager.getCacheManager()
  const room = await cacheManager.getContactOrRoomRawPayload(id)
  const roomAnnounce = await this.roomAnnounce(id)
  if (room) {
    room.announce = roomAnnounce
    return room
  } else {
    try {
      const rawRoom = await this.manager.getContactById(id)
      const avatar = await rawRoom.getProfilePicUrl() || ''
      const room = Object.assign(rawRoom, { avatar, announce: roomAnnounce })
      await cacheManager.setContactOrRoomRawPayload(id, room)
      return room
    } catch (error) {
      throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `roomRawPayload(${id}) not found.`)
    }
  }
}

export async function roomRawPayloadParser (this: PuppetWhatsApp, roomPayload: RoomPayload): Promise<PUPPET.payloads.Room> {
  const roomId = roomPayload.id._serialized
  try {
    const roomChat = await this.manager.getRoomChatById(roomId)
    const result = parserRoomRawPayload(roomPayload, roomChat)
    log.verbose(PRE, 'roomRawPayloadParser roomPayload(%s) roomChat(%s) result(%s)', JSON.stringify(roomPayload), JSON.stringify(roomChat), JSON.stringify(result))
    return result
  } catch (error) {
    log.error(PRE, `roomRawPayloadParser(${roomId}) failed, error message: ${(error as Error).message}`)
    throw WAError(WA_ERROR_TYPE.ERR_ROOM_NOT_FOUND, `roomRawPayloadParser(${roomId}) failed, error message: ${(error as Error).message}`)
  }
}
