import env from '../config/env.js'

const HEARTBEAT_CMD = { cmd: 1 }

function safeJsonParse(data) {
  if (typeof data !== 'string') return data
  try {
    return JSON.parse(data)
  } catch (error) {
    return data
  }
}

function getHostFromBaseUrl(baseUrl) {
  return String(baseUrl || '')
    .replace(/^https?:\/\//, '')
    .replace(/\/+$/, '')
}

function getShortStack() {
  try {
    const stack = new Error().stack || ''
    return stack.split('\n').slice(2, 8).join('\n')
  } catch (error) {
    return ''
  }
}

class WebSocketService {
  constructor() {
    this.socketTask = null
    this.isConnected = false
    this.isManualClose = false
    this.connectingPromise = null

    this.connectOptions = null

    this.reconnectAttempts = 0
    this.maxReconnectAttempts = 5
    this.reconnectInterval = 3000
    this.reconnectTimer = null

    this.heartbeatInterval = 20000
    this.heartbeatTimer = null

    this.listeners = {}
    this.useGlobalSocketApi = false
    this.globalSocketHandlers = null
  }

  on(eventName, handler) {
    if (!eventName || typeof handler !== 'function') return () => {}
    if (!this.listeners[eventName]) this.listeners[eventName] = new Set()
    this.listeners[eventName].add(handler)
    return () => this.off(eventName, handler)
  }

  off(eventName, handler) {
    if (!eventName || !this.listeners[eventName]) return
    if (!handler) {
      this.listeners[eventName].clear()
      return
    }
    this.listeners[eventName].delete(handler)
  }

  emit(eventName, payload) {
    const group = this.listeners[eventName]
    if (!group || !group.size) return
    group.forEach((fn) => {
      try {
        fn(payload)
      } catch (error) {
        console.error(`[WebSocket] listener error for ${eventName}:`, error)
      }
    })
  }

  buildSocketUrl(options = {}) {
    const role = options.role || 'parent'
    // const base = 'wss://yx.newfeifan.cn/btcSkt'
    // 开发地址（保留注释）：const base = 'ws://192.168.220.13:8080/btcSkt'
    const base = 'wss://m.hfdcschool.com/btcSkt'
    // 原 env 写法（保留注释）：const base = `wss://${getHostFromBaseUrl(env.baseUrl)}/btcSkt`

    if (role === 'device') {
      const ssDevId = options.ssDevId || ''
      if (!ssDevId) throw new Error('缺少 ssDevId')
      return `${base}?ssDevId=${encodeURIComponent(ssDevId)}`
    }

    const ssToken = options.ssToken || ''
    if (!ssToken) throw new Error('缺少 ssToken')
    return `${base}?ssToken=${encodeURIComponent(ssToken)}`
  }

  async connect(options = {}) {
    console.log('[WebSocket] connect called', {
      isConnected: this.isConnected,
      hasSocketTask: !!this.socketTask,
      hasConnectingPromise: !!this.connectingPromise,
      options,
      stack: getShortStack()
    })
    if (this.isConnected && this.socketTask && !options.forceReconnect) {
      console.log('[WebSocket] connect skip: already connected')
      return true
    }
    if (this.connectingPromise) {
      console.log('[WebSocket] connect reuse: existing connectingPromise')
      return this.connectingPromise
    }

    if (options.forceReconnect) {
      await this.disconnect()
    }
    if (this.socketTask && !this.isConnected && !options.forceReconnect) {
      console.warn('[WebSocket] found stale socketTask while disconnected, cleanup before reconnect')
      await this.disconnect()
    }

    const merged = {
      role: options.role || this.connectOptions?.role || 'parent',
      ssToken: options.ssToken || this.connectOptions?.ssToken || '',
      ssDevId: options.ssDevId || this.connectOptions?.ssDevId || '',
      heartbeat: options.heartbeat ?? this.connectOptions?.heartbeat ?? false,
      heartbeatInterval: options.heartbeatInterval || this.connectOptions?.heartbeatInterval || this.heartbeatInterval,
      autoReconnect: options.autoReconnect ?? this.connectOptions?.autoReconnect ?? true,
      maxReconnectAttempts: options.maxReconnectAttempts || this.connectOptions?.maxReconnectAttempts || this.maxReconnectAttempts,
      reconnectInterval: options.reconnectInterval || this.connectOptions?.reconnectInterval || this.reconnectInterval
    }

    this.connectOptions = merged
    this.heartbeatInterval = merged.heartbeatInterval
    this.maxReconnectAttempts = merged.maxReconnectAttempts
    this.reconnectInterval = merged.reconnectInterval
    this.isManualClose = false

    const wsUrl = this.buildSocketUrl(merged)
    console.log('[WebSocket] connecting...', {
      wsUrl,
      merged
    })
    const socketApi = typeof wx !== 'undefined' ? wx : uni
    this.connectingPromise = new Promise((resolve, reject) => {
      let settled = false
      let openTimeout = null
      const resolveOnce = (value) => {
        if (settled) return
        settled = true
        resolve(value)
      }
      const rejectOnce = (error) => {
        if (settled) return
        settled = true
        reject(error)
      }

      const handleOpen = () => {
        if (openTimeout) clearTimeout(openTimeout)
        this.isConnected = true
        this.reconnectAttempts = 0
        this.connectingPromise = null
        if (merged.heartbeat) {
          this.startHeartbeat()
        } else {
          this.stopHeartbeat()
        }
        this.emit('open', { url: wsUrl, options: merged })
        console.log('[WebSocket] open:', wsUrl)
        resolveOnce(true)
      }

      const handleMessage = (res) => {
        const data = safeJsonParse(res?.data)
        this.emit('message', data)
        if (data && typeof data === 'object' && data.cmd !== undefined) {
          this.emit(`cmd:${data.cmd}`, data)
          if (data.cmd === 51) this.emit('refresh', data)
        }
      }

      const handleError = (error) => {
        console.error('[WebSocket] error:', {
          wsUrl,
          error
        })
        this.emit('error', error)
        if (!this.isConnected && this.connectingPromise) {
          this.connectingPromise = null
          rejectOnce(error)
        }
      }

      const handleClose = (res) => {
        console.log('[WebSocket] close:', {
          wsUrl,
          isManualClose: this.isManualClose,
          reconnectEnabled: !!this.connectOptions?.autoReconnect,
          reconnectAttempts: this.reconnectAttempts,
          closeEvent: res
        })
        this.isConnected = false
        this.stopHeartbeat()
        this.emit('close', res)
        if (!this.isManualClose && this.connectOptions?.autoReconnect) {
          this.tryReconnect()
        }
      }

      let task = null
      try {
        task = socketApi.connectSocket({ url: wsUrl })
      } catch (error) {
        console.error('[WebSocket] connectSocket throw:', { wsUrl, error })
        this.connectingPromise = null
        rejectOnce(error)
        return
      }

      if (task && typeof task.onOpen === 'function') {
        this.useGlobalSocketApi = false
        this.socketTask = task
        task.onOpen(handleOpen)
        task.onMessage(handleMessage)
        task.onError(handleError)
        task.onClose(handleClose)
        console.log('[WebSocket] using task-level socket handlers')
        return
      }

      if (
        typeof socketApi.onSocketOpen !== 'function' ||
        typeof socketApi.onSocketMessage !== 'function' ||
        typeof socketApi.onSocketError !== 'function' ||
        typeof socketApi.onSocketClose !== 'function'
      ) {
        this.connectingPromise = null
        rejectOnce(new Error('创建 WebSocket 失败'))
        return
      }

      this.useGlobalSocketApi = true
      this.socketTask = { __global: true }
      const openHandler = () => handleOpen()
      const messageHandler = (res) => handleMessage(res)
      const errorHandler = (err) => handleError(err)
      const closeHandler = (res) => handleClose(res)
      this.globalSocketHandlers = { openHandler, messageHandler, errorHandler, closeHandler }
      socketApi.onSocketOpen(openHandler)
      socketApi.onSocketMessage(messageHandler)
      socketApi.onSocketError(errorHandler)
      socketApi.onSocketClose(closeHandler)
      console.log('[WebSocket] using global socket handlers')
      openTimeout = setTimeout(() => {
        this.connectingPromise = null
        rejectOnce(new Error('WebSocket 连接超时(未收到 onSocketOpen)'))
      }, 15000)
    })

    return this.connectingPromise
  }

  tryReconnect() {
    if (this.reconnectTimer) return
    if (this.reconnectAttempts >= this.maxReconnectAttempts) return

    this.reconnectAttempts += 1
    console.warn('[WebSocket] schedule reconnect', {
      reconnectAttempts: this.reconnectAttempts,
      maxReconnectAttempts: this.maxReconnectAttempts,
      reconnectInterval: this.reconnectInterval,
      connectOptions: this.connectOptions
    })
    this.reconnectTimer = setTimeout(async () => {
      this.reconnectTimer = null
      try {
        console.warn('[WebSocket] reconnecting now', {
          reconnectAttempts: this.reconnectAttempts
        })
        await this.connect(this.connectOptions || {})
      } catch (error) {
        console.error('[WebSocket] reconnect failed', error)
        this.tryReconnect()
      }
    }, this.reconnectInterval)
  }

  startHeartbeat() {
    this.stopHeartbeat()
    console.log('[WebSocket] heartbeat start', { heartbeatInterval: this.heartbeatInterval })
    this.heartbeatTimer = setInterval(() => {
      if (!this.isConnected) return
      console.log('[WebSocket] heartbeat send', HEARTBEAT_CMD)
      this.send(HEARTBEAT_CMD).catch(() => {})
    }, this.heartbeatInterval)
  }

  stopHeartbeat() {
    if (!this.heartbeatTimer) return
    console.log('[WebSocket] heartbeat stop', {
      isConnected: this.isConnected,
      stack: getShortStack()
    })
    clearInterval(this.heartbeatTimer)
    this.heartbeatTimer = null
  }

  async disconnect() {
    console.log('[WebSocket] disconnect called', {
      hasSocketTask: !!this.socketTask,
      useGlobalSocketApi: this.useGlobalSocketApi
    })
    this.isManualClose = true
    this.stopHeartbeat()

    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }

    const socketApi = typeof wx !== 'undefined' ? wx : uni
    return new Promise((resolve) => {
      if (!this.socketTask) {
        this.socketTask = null
        this.useGlobalSocketApi = false
        this.isConnected = false
        this.connectingPromise = null
        resolve()
        return
      }

      if (this.useGlobalSocketApi || this.socketTask.__global) {
        if (this.globalSocketHandlers) {
          const { openHandler, messageHandler, errorHandler, closeHandler } = this.globalSocketHandlers
          if (typeof socketApi.offSocketOpen === 'function') socketApi.offSocketOpen(openHandler)
          if (typeof socketApi.offSocketMessage === 'function') socketApi.offSocketMessage(messageHandler)
          if (typeof socketApi.offSocketError === 'function') socketApi.offSocketError(errorHandler)
          if (typeof socketApi.offSocketClose === 'function') socketApi.offSocketClose(closeHandler)
          this.globalSocketHandlers = null
        }
        socketApi.closeSocket({
          complete: () => {
            this.socketTask = null
            this.useGlobalSocketApi = false
            this.isConnected = false
            this.connectingPromise = null
            resolve()
          }
        })
        return
      }

      if (typeof this.socketTask.close !== 'function') {
        this.socketTask = null
        this.useGlobalSocketApi = false
        this.isConnected = false
        this.connectingPromise = null
        resolve()
        return
      }

      this.socketTask.close({
        complete: () => {
          this.socketTask = null
          this.useGlobalSocketApi = false
          this.isConnected = false
          this.connectingPromise = null
          resolve()
        }
      })
    })
  }

  async send(payload) {
    if (!this.socketTask || !this.isConnected) {
      throw new Error('WebSocket 未连接')
    }

    const socketApi = typeof wx !== 'undefined' ? wx : uni
    return new Promise((resolve, reject) => {
      if (this.useGlobalSocketApi || this.socketTask.__global) {
        socketApi.sendSocketMessage({
          data: JSON.stringify(payload),
          success: () => resolve(true),
          fail: (error) => reject(error)
        })
        return
      }
      if (typeof this.socketTask.send !== 'function') {
        reject(new Error('WebSocket 发送不可用'))
        return
      }
      this.socketTask.send({
        data: JSON.stringify(payload),
        success: () => resolve(true),
        fail: (error) => reject(error)
      })
    })
  }

  async ensureConnected(options = {}) {
    console.log('[WebSocket] ensureConnected called', {
      isConnected: this.isConnected,
      hasSocketTask: !!this.socketTask,
      options,
      stack: getShortStack()
    })
    if (this.isConnected && this.socketTask) return true
    await this.connect(options)
    return true
  }
}

const websocketService = new WebSocketService()
export default websocketService
