// 运行在 Electron 主进程 下的插件入口

import { BrowserWindow, dialog, ipcMain } from 'electron'
import path from 'node:path'
import fs from 'node:fs'
import { Config } from '../common/types'
import {
  CHANNEL_CHECK_VERSION,
  CHANNEL_ERROR,
  CHANNEL_GET_CONFIG,
  CHANNEL_LOG,
  CHANNEL_SELECT_FILE,
  CHANNEL_SET_CONFIG,
  CHANNEL_UPDATE,
} from '../common/channels'
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
import { DATA_DIR, TEMP_DIR } from '../common/utils'
import {
  getGroupMember,
  llonebotError,
  setSelfInfo,
  getSelfInfo,
  getSelfUid,
  getSelfUin,
  addMsgCache
} from '../common/data'
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
import { OB11Constructor } from '../onebot11/constructor'
import {
  FriendRequestNotify,
  GroupNotifies,
  GroupNotifyTypes,
  RawMessage,
  BuddyReqType,
} from '../ntqqapi/types'
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
import { postOb11Event } from '../onebot11/server/post-ob11-event'
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest'
import { MessageUnique } from '../common/utils/MessageUnique'
import { setConfig } from './setConfig'
import { NTQQUserApi, NTQQGroupApi } from '../ntqqapi/api'
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
import { log } from '../common/utils/log'
import { getConfigUtil } from '../common/config'
import { checkFfmpeg } from '../common/utils/video'
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
import '../ntqqapi/wrapper'
import { NTEventDispatch } from '../common/utils/EventTask'
import { wrapperConstructor, getSession } from '../ntqqapi/wrapper'
import { Peer } from '../ntqqapi/types'

let mainWindow: BrowserWindow | null = null

// 加载插件时触发
function onLoad() {
  ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => {
    return checkNewVersion()
  })
  ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => {
    return upgradeLLOneBot()
  })
  ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
    const selectPath = new Promise<string>((resolve, reject) => {
      dialog
        .showOpenDialog({
          title: '请选择ffmpeg',
          properties: ['openFile'],
          buttonLabel: '确定',
        })
        .then((result) => {
          log('选择文件', result)
          if (!result.canceled) {
            const _selectPath = path.join(result.filePaths[0])
            resolve(_selectPath)
            // let config = getConfigUtil().getConfig()
            // config.ffmpeg = path.join(result.filePaths[0]);
            // getConfigUtil().setConfig(config);
          }
          resolve('')
        })
        .catch((err) => {
          reject(err)
        })
    })
    try {
      return await selectPath
    } catch (e) {
      log('选择文件出错', e)
      return ''
    }
  })
  if (!fs.existsSync(DATA_DIR)) {
    fs.mkdirSync(DATA_DIR, { recursive: true })
  }
  ipcMain.handle(CHANNEL_ERROR, async (event, arg) => {
    const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
    llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常'
    let { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError
    let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
    error = error.replace('\n\n', '\n')
    error = error.trim()
    log('查询llonebot错误信息', error)
    return error
  })
  ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => {
    const config = getConfigUtil().getConfig()
    return config
  })
  ipcMain.on(CHANNEL_SET_CONFIG, (event, ask: boolean, config: Config) => {
    if (!ask) {
      setConfig(config)
        .then()
        .catch((e) => {
          log('保存设置失败', e.stack)
        })
      return
    }
    dialog
      .showMessageBox(mainWindow!, {
        type: 'question',
        buttons: ['确认', '取消'],
        defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认"
        title: '确认保存',
        message: '是否保存?',
        detail: 'LLOneBot配置已更改,是否保存?',
      })
      .then((result) => {
        if (result.response === 0) {
          setConfig(config)
            .then()
            .catch((e) => {
              log('保存设置失败', e.stack)
            })
        }
        else {
        }
      })
      .catch((err) => {
        log('保存设置询问弹窗错误', err)
      })
  })

  ipcMain.on(CHANNEL_LOG, (event, arg) => {
    log(arg)
  })

  async function postReceiveMsg(msgList: RawMessage[]) {
    const { debug, reportSelfMessage } = getConfigUtil().getConfig()
    for (let message of msgList) {
      // 过滤启动之前的消息
      // log('收到新消息', message);
      if (parseInt(message.msgTime) < startTime / 1000) {
        continue
      }
      // log("收到新消息", message.msgId, message.msgSeq)
      const peer: Peer = {
        chatType: message.chatType,
        peerUid: message.peerUid
      }
      message.msgShortId = MessageUnique.createMsg(peer, message.msgId)
      addMsgCache(message)

      OB11Constructor.message(message)
        .then((msg) => {
          if (!debug && msg.message.length === 0) {
            return
          }
          const isSelfMsg = msg.user_id.toString() === getSelfUin()
          if (isSelfMsg && !reportSelfMessage) {
            return
          }
          if (isSelfMsg) {
            msg.target_id = parseInt(message.peerUin)
          }
          postOb11Event(msg)
          // log("post msg", msg)
        })
        .catch((e) => log('constructMessage error: ', e.stack.toString()))
      OB11Constructor.GroupEvent(message).then((groupEvent) => {
        if (groupEvent) {
          // log("post group event", groupEvent);
          postOb11Event(groupEvent)
        }
      })
      OB11Constructor.PrivateEvent(message).then((privateEvent) => {
        //log(message)
        if (privateEvent) {
          // log("post private event", privateEvent);
          postOb11Event(privateEvent)
        }
      })
      // OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
      //   log(message)
      //   if (friendAddEvent) {
      //     // log("post friend add event", friendAddEvent);
      //     postOb11Event(friendAddEvent)
      //   }
      // })
    }
  }

  async function startReceiveHook() {
    startHook()
    registerReceiveHook<{
      msgList: Array<RawMessage>
    }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
      try {
        await postReceiveMsg(payload.msgList)
      } catch (e: any) {
        log('report message error: ', e.stack.toString())
      }
    })
    const recallMsgIds: string[] = [] // 避免重复上报
    registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
      for (const message of payload.msgList) {
        if (message.recallTime != '0') {
          if (recallMsgIds.includes(message.msgId)) {
            continue
          }
          recallMsgIds.push(message.msgId)
          const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId)
          if (!oriMessageId) {
            continue
          }
          OB11Constructor.RecallEvent(message, oriMessageId).then((recallEvent) => {
            if (recallEvent) {
              //log('post recall event', recallEvent)
              postOb11Event(recallEvent)
            }
          })
        }
      }
    })
    registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, async (payload) => {
      const { reportSelfMessage } = getConfigUtil().getConfig()
      if (!reportSelfMessage) {
        return
      }
      // log("reportSelfMessage", payload)
      try {
        await postReceiveMsg([payload.msgRecord])
      } catch (e: any) {
        log('report self message error: ', e.stack.toString())
      }
    })
    registerReceiveHook<{
      doubt: boolean
      oldestUnreadSeq: string
      unreadCount: number
    }>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => {
      if (payload.unreadCount) {
        // log("开始获取群通知详情")
        let notify: GroupNotifies
        try {
          notify = await NTQQGroupApi.getGroupNotifies()
        } catch (e) {
          // log("获取群通知详情失败", e);
          return
        }

        const notifies = notify.notifies.slice(0, payload.unreadCount)
        // log("获取群通知详情完成", notifies, payload);

        for (const notify of notifies) {
          try {
            notify.time = Date.now()
            const notifyTime = parseInt(notify.seq) / 1000
            if (notifyTime < startTime) {
              continue
            }
            log('收到群通知', notify)
            const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
            if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
              log('有成员退出通知', notify)
              try {
                const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
                let operatorId = member1.uin
                let subType: GroupDecreaseSubType = 'leave'
                if (notify.user2.uid) {
                  // 是被踢的
                  const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid)
                  operatorId = member2?.uin!
                  subType = 'kick'
                }
                let groupDecreaseEvent = new OB11GroupDecreaseEvent(
                  parseInt(notify.group.groupCode),
                  parseInt(member1.uin),
                  parseInt(operatorId),
                  subType,
                )
                postOb11Event(groupDecreaseEvent, true)
              } catch (e: any) {
                log('获取群通知的成员信息失败', notify, e.stack.toString())
              }
            }
            else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) {
              log('有加群请求')
              let requestQQ = ''
              try {
                // uid-->uin
                requestQQ = (await NTQQUserApi.getUinByUid(notify.user1.uid))
                if (isNaN(parseInt(requestQQ))) {
                  requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
                }
              } catch (e) {
                log('获取加群人QQ号失败 Uid:', notify.user1.uid, e)
              }
              let invitorId: string
              if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) {
                // groupRequestEvent.sub_type = 'invite'
                try {
                  // uid-->uin
                  invitorId = (await NTQQUserApi.getUinByUid(notify.user2.uid))
                  if (isNaN(parseInt(invitorId))) {
                    invitorId = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid)).uin
                  }
                } catch (e) {
                  invitorId = ''
                  log('获取邀请人QQ号失败 Uid:', notify.user2.uid, e)
                }
              }
              const groupRequestEvent = new OB11GroupRequestEvent(
                parseInt(notify.group.groupCode),
                parseInt(requestQQ) || 0,
                flag,
                notify.postscript,
                invitorId! === undefined ? undefined : +invitorId,
                'add'
              )
              postOb11Event(groupRequestEvent)
            }
            else if (notify.type == GroupNotifyTypes.INVITE_ME) {
              log('收到邀请我加群通知')
              const userId = (await NTQQUserApi.getUinByUid(notify.user2.uid)) || ''
              const groupInviteEvent = new OB11GroupRequestEvent(
                parseInt(notify.group.groupCode),
                parseInt(userId),
                flag,
                undefined,
                undefined,
                'invite'
              )
              postOb11Event(groupInviteEvent)
            }
          } catch (e: any) {
            log('解析群通知失败', e.stack.toString())
          }
        }
      }
      else if (payload.doubt) {
        // 可能有群管理员变动
      }
    })

    registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, async (payload) => {
      for (const req of payload.data.buddyReqs) {
        if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
          continue
        }
        let userId = 0
        try {
          const requesterUin = await NTQQUserApi.getUinByUid(req.friendUid)
          userId = parseInt(requesterUin!)
        } catch (e) {
          log('获取加好友者QQ号失败', e)
        }
        const flag = req.friendUid + '|' + req.reqTime
        const comment = req.extWords
        const friendRequestEvent = new OB11FriendRequestEvent(
          userId,
          comment,
          flag
        )
        postOb11Event(friendRequestEvent)
      }
    })
  }

  let startTime = 0 // 毫秒

  async function start(uid: string, uin: string) {
    log('llonebot pid', process.pid)
    const config = getConfigUtil().getConfig()
    if (!config.enableLLOB) {
      llonebotError.otherError = 'LLOneBot 未启动'
      log('LLOneBot 开关设置为关闭,不启动LLOneBot')
      return
    }
    if (!fs.existsSync(TEMP_DIR)) {
      fs.mkdirSync(TEMP_DIR, { recursive: true })
    }
    llonebotError.otherError = ''
    startTime = Date.now()
    NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: getSession()! })
    MessageUnique.init(uin)

    log('start activate group member info')
    // 下面两个会导致CPU占用过高,QQ卡死
    // NTQQGroupApi.activateMemberInfoChange().then().catch(log)
    // NTQQGroupApi.activateMemberListChange().then().catch(log)
    startReceiveHook().then()

    if (config.ob11.enableHttp) {
      ob11HTTPServer.start(config.ob11.httpPort)
    }
    if (config.ob11.enableWs) {
      ob11WebsocketServer.start(config.ob11.wsPort)
    }
    if (config.ob11.enableWsReverse) {
      ob11ReverseWebsockets.start()
    }
    if (config.ob11.enableHttpHeart) {
      httpHeart.start()
    }

    log('LLOneBot start')
  }

  const intervalId = setInterval(() => {
    const current = getSelfInfo()
    if (!current.uin) {
      setSelfInfo({
        uin: globalThis.authData?.uin,
        uid: globalThis.authData?.uid,
        nick: current.uin,
      })
    }
    if (current.uin && getSession()) {
      clearInterval(intervalId)
      start(current.uid, current.uin)
    }
  }, 600)
}

// 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) {
  if (getSelfUid()) {
    return
  }
  mainWindow = window
  log('window create', window.webContents.getURL().toString())
  try {
    hookNTQQApiCall(window)
    hookNTQQApiReceive(window)
  } catch (e: any) {
    log('LLOneBot hook error: ', e.toString())
  }
}

try {
  onLoad()
} catch (e: any) {
  console.log(e.toString())
}

// 这两个函数都是可选的
export { onBrowserWindowCreated }