import fs from 'node:fs'
import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { MessageUnique } from '../common/utils/messageUnique'
import { NTEventDispatch } from '../common/utils/eventTask'
import { wrapperConstructor, getSession } from './wrapper'
import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc'
import { NTMethod } from './ntcall'
import {
  RawMessage,
  GroupNotify,
  FriendRequestNotify,
  FriendRequest,
  GroupMember,
  CategoryFriend,
  SimpleInfo,
  User,
  ChatType
} from './types'
import { selfInfo } from '../common/globalVars'
import { version } from '../version'

declare module 'cordis' {
  interface Context {
    app: Core
  }
  interface Events {
    'nt/message-created': (input: RawMessage[]) => void
    'nt/message-deleted': (input: RawMessage[]) => void
    'nt/message-sent': (input: RawMessage[]) => void
    'nt/group-notify': (input: GroupNotify[]) => void
    'nt/friend-request': (input: FriendRequest[]) => void
    'nt/group-member-info-updated': (input: { groupCode: string; members: GroupMember[] }) => void
    'nt/friend-list-updated': (input: { groupCode: string; members: GroupMember[] }) => void
  }
}

class Core extends Service {
  static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi']

  constructor(protected ctx: Context, public config: Core.Config) {
    super(ctx, 'app', true)
  }

  public start() {
    llonebotError.otherError = ''
    const WrapperSession = getSession()
    if (WrapperSession) {
      NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession })
    }
    MessageUnique.init(selfInfo.uin)
    this.registerListener()
    this.ctx.logger.info(`LLOneBot/${version}`)
  }

  private registerListener() {
    registerReceiveHook<{
      data: CategoryFriend[]
    }>(ReceiveCmdS.FRIENDS, (payload) => {
      type V2data = { userSimpleInfos: Map<string, SimpleInfo> }
      let friendList: User[] = [];
      if ((payload as any).userSimpleInfos) {
        friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => {
          return {
            ...v.coreInfo,
          }
        })
      } else {
        for (const fData of payload.data) {
          friendList.push(...fData.buddyList)
        }
      }
      this.ctx.logger.info('好友列表变动', friendList.length)
      for (const friend of friendList) {
        this.ctx.ntMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend })
      }
    })

    // 自动清理新消息文件
    registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
      if (!this.config.autoDeleteFile) {
        return
      }
      for (const message of payload.msgList) {
        for (const msgElement of message.elements) {
          setTimeout(() => {
            const picPath = msgElement.picElement?.sourcePath
            const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
            const pttPath = msgElement.pttElement?.filePath
            const filePath = msgElement.fileElement?.filePath
            const videoPath = msgElement.videoElement?.filePath
            const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!]
            const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
            if (msgElement.picElement) {
              pathList.push(...Object.values(msgElement.picElement.thumbPath))
            }
            for (const path of pathList) {
              if (path) {
                fs.unlink(picPath, () => {
                  this.ctx.logger.info('删除文件成功', path)
                })
              }
            }
          }, this.config.autoDeleteFileSecond! * 1000)
        }
      }
    })

    registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
      Object.assign(selfInfo, { online: info.info.status !== 20 })
    })

    const activatedPeerUids: string[] = []
    registerReceiveHook<{
      changedRecentContactLists: {
        listType: number
        sortedContactList: string[]
        changedList: {
          id: string // peerUid
          chatType: ChatType
        }[]
      }[]
    }>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
      for (const recentContact of payload.changedRecentContactLists) {
        for (const changedContact of recentContact.changedList) {
          if (activatedPeerUids.includes(changedContact.id)) continue
          activatedPeerUids.push(changedContact.id)
          const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
          if (changedContact.chatType === ChatType.temp) {
            this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => {
              this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
                const lastTempMsg = msgList.at(-1)
                if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) {
                  this.ctx.parallel('nt/message-created', [lastTempMsg!])
                }
              })
            })
          }
          else {
            this.ctx.ntMsgApi.activateChat(peer)
          }
        }
      }
    })

    registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => {
      const peerUid = payload[0] as string
      this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid)
      let chatType = ChatType.friend
      if (isNumeric(peerUid)) {
        chatType = ChatType.group
      }
      else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) {
        chatType = ChatType.temp
      }
      const peer = { peerUid, chatType }
      await this.ctx.sleep(1000)
      this.ctx.ntMsgApi.activateChat(peer).then((r) => {
        this.ctx.logger.info('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
      })
    })

    registerReceiveHook<{
      groupCode: string
      dataSource: number
      members: Set<GroupMember>
    }>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
      const groupCode = payload.groupCode
      const members = Array.from(payload.members.values())
      this.ctx.parallel('nt/group-member-info-updated', { groupCode, members })
    })

    registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => {
      this.ctx.parallel('nt/message-created', payload.msgList)
    })

    const recallMsgIds: string[] = [] // 避免重复上报
    registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => {
      const list = payload.msgList.filter(v => {
        if (recallMsgIds.includes(v.msgId)) {
          return false
        }
        recallMsgIds.push(v.msgId)
        return true
      })
      this.ctx.parallel('nt/message-deleted', list)
    })

    registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
      const { msgId, chatType, peerUid } = payload.msgRecord
      const peer = {
        chatType,
        peerUid
      }
      MessageUnique.createMsg(peer, msgId)
      if (!this.config.reportSelfMessage) {
        return
      }
      this.ctx.parallel('nt/message-sent', [payload.msgRecord])
    })

    const groupNotifyFlags: string[] = []
    registerReceiveHook<{
      doubt: boolean
      oldestUnreadSeq: string
      unreadCount: number
    }>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => {
      if (payload.unreadCount) {
        let notifies: GroupNotify[]
        try {
          notifies = (await this.ctx.ntGroupApi.getSingleScreenNotifies(14)).slice(0, payload.unreadCount)
        } catch (e) {
          return
        }
        const list = notifies.filter(v => {
          const flag = v.group.groupCode + '|' + v.seq + '|' + v.type
          if (groupNotifyFlags.includes(flag)) {
            return false
          }
          groupNotifyFlags.push(flag)
          return true
        })
        this.ctx.parallel('nt/group-notify', list)
      }
    })

    registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => {
      this.ctx.parallel('nt/friend-request', payload.data.buddyReqs)
    })
  }
}

namespace Core {
  export interface Config extends LLOBConfig {
  }
}

export default Core