import type Message from '@/mode/Message'
import type Receipt from '@/mode/Receipt'
import SendCode from '@/utils/SendCode'
import ChatUtils from '@/utils/ChatUtils'
import vimConfig from '@/config/VimConfig'
import Auth from '@/api/Auth'
import ChatType from '@/utils/ChatType'
import MessageType from '@/utils/MessageType'
import {useUserStore} from '@/store/userStore'
import {useChatStore} from '@/store/chatStore'
import {useFriendStore} from '@/store/friendStore'
import {useGroupStore} from '@/store/groupStore'
import MessageUtils from '@/utils/MessageUtils'
import VimPlugin from "@/plugins/VimPlugin";

const ready = `{"code":${SendCode.READY}}`
const ping = `{"code":${SendCode.PING}}`

class WsRequest {
  lockReconnect: boolean
  url: string | undefined
  //是否主动关闭
  closeByUser: boolean
  //心跳检测 多少秒执行检测
  timeout: number
  //重连超时时间
  timeoutError: number
  heartTask: number | null
  reconnectTimeoutTask: number | null
  socket: UniApp.SocketTask | null
  uuid: string

  private static instance: WsRequest

  private constructor() {
    this.lockReconnect = false //避免重复连接
    this.url = ''
    //是否主动关闭
    this.closeByUser = false
    //心跳检测 多少秒执行检测
    this.timeout = 3000
    //超过多少秒没反应就重连
    this.timeoutError = 5000
    this.heartTask = null
    this.reconnectTimeoutTask = null
    this.socket = null
    this.uuid = `${new Date().getTime()}`;
  }

  static getInstance() {
    if (!this.instance) {
      this.instance = new WsRequest()
    }
    return this.instance
  }

  public init(): void {
    this.closeByUser = false
    // this.url = `${vimConfig.wsProtocol}://${vimConfig.host}:${vimConfig.wsPort}?token=${Auth.getToken()}&client=${vimConfig.client}&uuid=${this.uuid}`
	this.url = `${vimConfig.wsProtocol}://newim.taohaowan.com/ws?token=${Auth.getToken()}&client=${vimConfig.client}&uuid=${this.uuid}`
    this.socket = uni.connectSocket({
      url: this.url, //接口地址。
      fail: (err) => {
        console.log(err, '连接错误')
      }
    })
    this.socket.onOpen( () => {
      //告知服务器准备就绪
      this.send(ready)
      // 开启检测
      this.reset()
    })

    // 如果希望websocket连接一直保持，在close或者error上绑定重新连接方法。
    this.socket.onClose(() => {
      if (!this.closeByUser) {
        this.reconnect()
      }
    })

    this.socket.onError (() => {
      this.reconnect()
    })

    this.socket.onMessage((res) => {
      const data = res.data
      //防止每次心跳都要进行 JSON.parse 优化性能
      if (data === ping) {
        this.reset()
        return
      }
      const sendInfo = JSON.parse(data)
      // 真正的消息类型
      if (sendInfo.code === SendCode.MESSAGE) {
        this.onmessage(sendInfo.message)
      } else if (sendInfo.code === SendCode.OTHER_LOGIN && this.uuid !== sendInfo.message.uuid) {
        MessageUtils.error('账号已经在别处登录')
        Auth.logout()
      }else if (sendInfo.code === SendCode.NEW_FRIEND) {
        useFriendStore().loadValidateList()
        useFriendStore().loadData()
      } else if (sendInfo.code === SendCode.GROUP_VALIDATE) {
        useGroupStore().loadWaitCheckList()
      }
      else if (sendInfo.code === SendCode.READ){
        useChatStore().setLastReadTime(sendInfo.message)
      }
      else {
        VimPlugin.messageListener(sendInfo)
      }
      //接受任何消息都说明当前连接是正常的
      this.reset()
    })
  }

  /**
   * 发送状态
   * @param value
   */
  send(value: string): void {
    this.socket?.send({
      data:value
    })
  }

  /**
   * 收到消息
   * @param message 消息
   */
  onmessage = (message: Message): void => {
    const user = useUserStore().getUser()
    //群聊里面，自己发的消息不再显示
    if (user?.id === message.fromId) {
      message.mine = true
    }
    //友聊换chatId,chatId 不一样
    if (ChatType.FRIEND === message.type && user?.id !== message.fromId) {
      message.chatId = message.fromId
    }
    if (message.messageType === MessageType.back) {
      useChatStore().backMessage(message)
    } else {
      useChatStore().pushMessage(message)
    }
    this.callback(message)
  }

  /**
   * 发送真正的聊天消息
   * @param message 消息
   */
  sendMessage(message: Message): void {
    const sendInfo = {
      code: SendCode.MESSAGE,
      message: message
    }
    this.send(JSON.stringify(sendInfo))
  }

  /**
   * 发送已读取消息
   * @param receipt 消息读取回执
   */
  sendRead(receipt: Receipt): void {
    const sendInfo = {
      code: SendCode.READ,
      message: receipt
    }
    this.send(JSON.stringify(sendInfo))
  }

  /**
   *  reset和start方法主要用来控制心跳的定时。
   */
  reset(): void {
    // 清除定时器重新发送一个心跳信息
    if (this.heartTask) {
      clearTimeout(this.heartTask)
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask)
    }
    this.lockReconnect = false
    this.heartTask = setTimeout(() => {
      //这里发送一个心跳，后端收到后，返回一个心跳消息，
      //onmessage拿到返回的心跳就说明连接正常
      this.send(ping)
    }, this.timeout)
    //onmessage拿到消息就会清理 reconnectTimeoutTask，如果没有清理，就会执行重连
    this.reconnectTimeoutTask = setTimeout(() => {
      this.reconnect()
    }, this.timeoutError)
  }


  /**
   *  立即验证连接有效性
   *  重置心跳检测和重连检测
   *  立刻发送一个心跳信息
   *  如果没有收到消息，就会执行重连
   */
  checkStatus(): void {
    // 清除定时器重新发送一个心跳信息
    if (this.heartTask) {
      clearTimeout(this.heartTask)
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask)
    }
    this.lockReconnect = false
    this.send(ping)
    //onmessage拿到消息就会清理 reconnectTimeoutTask，如果没有清理，就会执行重连
    this.reconnectTimeoutTask = setTimeout(() => {
      this.reconnect()
    }, this.timeoutError - this.timeout)
  }
  /**
   * 收到消息的回调函数
   */
  callback = (message: Message) => {
  }
  // 重连
  reconnect(): void {
    // 防止多个方法调用，多处重连
    if (this.lockReconnect) {
      return
    }
    this.lockReconnect = true
    //没连接上会一直重连，设置延迟避免请求过多
    this.reconnectTimeoutTask = setTimeout(() => {
      // 重新连接
      this.init()
      this.lockReconnect = false
    }, this.timeoutError)
  }

  // 手动关闭
  close(closeByUser:boolean): void {
    this.lockReconnect = false
    //主动关闭
    if (this.heartTask) {
      clearTimeout(this.heartTask)
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask)
    }
    this.closeByUser = closeByUser
    if (this.socket) {
      this.socket.close({ code: 1000, reason: '用户主动关闭' })
    }
  }
}

export default WsRequest
