import { getWsUrl } from '../api/axios'
import useOrderStore from '../store/order'

interface NotificationOptions {
  body: string
  icon: string
  data: string
  requireInteraction: boolean
}

export interface WebSocketMessage {
  code?: string
  content?: string
  type?: string
  data?: unknown
  [key: string]: string | unknown | undefined
}

interface InitOptions {
  account: string
  factoryNo: string
}

type MessageCallback = (
  data: WebSocketMessage,
  sendSystemMessage: (msg: string) => void,
) => void
type OpenCallback = () => void

function sendSystemMessage(msg: string): void {
  if (window.Notification && Notification.permission === 'granted') {
    const notificationOptions: NotificationOptions = {
      body: msg,
      icon: './favicon.ico',
      data: 'I like peas.',
      requireInteraction: true,
    }

    const n = new Notification('消息通知', notificationOptions)

    n.onclick = (event: Event) => {
      event.preventDefault()
      window.open('/home', '_blank')
    }

    setTimeout(() => {
      n.close()
    }, 10000)
  }
}

const showReconnectingMsg = (): void => {
  ElMessageBox.alert('消息服务已断开，正在重新连接，请稍候', {
    showClose: true,
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
}

class Im {
  private socket: WebSocket | null = null
  private _wsUrl: string = ''
  // private userId: string = ''
  private _onMessageCallback?: MessageCallback
  private _onOpenCallback?: OpenCallback
  private _heartbeatTimer: number | null = null
  private _heartbeatTimeoutTimer: number | null = null
  private _reconnectingTimer: number | null = null
  private num: number = 0

  constructor() {
    this._onWsOpen = this._onWsOpen.bind(this)
    this._onWsMessage = this._onWsMessage.bind(this)
    this._onWsClose = this._onWsClose.bind(this)
    this._onWsError = this._onWsError.bind(this)
  }

  private _onWsOpen(): void {
    console.log('服务器连接成功')
    localStorage.setItem('socket_connect', 'online')
    this._onOpenCallback?.()
    this._startHeartbeat()
  }

  private _onWsMessage(event: MessageEvent): void {
    let data: WebSocketMessage = {}
    if (typeof event.data === 'string') {
      try {
        data = JSON.parse(event.data)
      } catch (error) {
        data = {}
      }
    }
    this._onHeartbeatMessage()
    this._onMessageCallback?.(data, sendSystemMessage)
  }

  private _onWsClose(): void {
    console.log('服务器关闭')
    this._destroyWebSocket(true)
  }

  private _onWsError(): void {
    console.log('连接出错')
    this._destroyWebSocket(true)
  }

  private _sendHeartbeat(): void {
    if (!this.socket) return
    this.send({ code: 'HEALTH' })

    if (this._heartbeatTimeoutTimer) {
      window.clearTimeout(this._heartbeatTimeoutTimer)
    }

    this._heartbeatTimeoutTimer = window.setTimeout(() => {
      this._destroyWebSocket(true)
    }, 5 * 1000)
  }

  private _onHeartbeatMessage(): void {
    console.log('心跳')
    useOrderStore().setSocketConnect('online')
    if (this._heartbeatTimeoutTimer) {
      window.clearTimeout(this._heartbeatTimeoutTimer)
    }
  }

  private _startHeartbeat(): void {
    this._stopHeartbeat()
    this._sendHeartbeat()
    this._heartbeatTimer = window.setInterval(
      () => this._sendHeartbeat(),
      10 * 1000,
    )
  }

  private _stopHeartbeat(): void {
    if (this._heartbeatTimer) {
      window.clearInterval(this._heartbeatTimer)
    }
    if (this._heartbeatTimeoutTimer) {
      window.clearTimeout(this._heartbeatTimeoutTimer)
    }
  }

  private _scheduleReconnect(): void {
    if (!this.num) this.num = 0
    this.num++

    if (this.num > 5) {
      ElMessageBox.alert('尝试重连消息服务失败，请刷新重试')
      return
    }

    showReconnectingMsg()
    this._reconnectingTimer = window.setTimeout(() => {
      this._createWebSocket()
    }, 2000)
  }

  private _destroyWebSocket(reconnect?: boolean): void {
    if (!this.socket) return
    this._stopHeartbeat()

    if (this._reconnectingTimer) {
      window.clearTimeout(this._reconnectingTimer)
    }

    this.socket.removeEventListener('open', this._onWsOpen)
    this.socket.removeEventListener('message', this._onWsMessage)
    this.socket.removeEventListener('close', this._onWsClose)
    this.socket.removeEventListener('error', this._onWsError)
    this.socket.close(1000)
    this.socket = null
    localStorage.removeItem('socket_connect')
    useOrderStore().setSocketConnect('offline')

    if (reconnect) this._scheduleReconnect()
  }

  private _createWebSocket(): void {
    if (!this._wsUrl) return

    const socket = new WebSocket(this._wsUrl)
    socket.addEventListener('open', this._onWsOpen)
    socket.addEventListener('message', this._onWsMessage)
    socket.addEventListener('close', this._onWsClose)
    socket.addEventListener('error', this._onWsError)
    this.socket = socket
  }

  init(
    options: InitOptions,
    msgfunc?: MessageCallback,
    openfunc?: OpenCallback,
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      const { account, factoryNo } = options
      const socket_connect = localStorage.getItem('socket_connect')
      if (socket_connect === 'online') {
        resolve()
        return
      }

      if (!window.WebSocket) {
        reject(new Error('WebSocket is not supported'))
        return
      }

      this._onMessageCallback = msgfunc
      this._onOpenCallback = () => {
        openfunc?.()
        resolve()
      }

      this._destroyWebSocket()
      this._wsUrl = `${getWsUrl()}/ws/websocket/${factoryNo}/${account}`
      this._createWebSocket()
    })
  }

  send(options: WebSocketMessage): void {
    this.socket?.send(JSON.stringify(options))
  }

  close(): void {
    this._destroyWebSocket()
  }
}

export default new Im()
