import store from '@/store'
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth'
import { getTsbWsBind } from '@/api/tsb/ws'
import { initTsbWsRouter } from '@/utils/tsbWsRouter'
import {
  buildDeviceSession,
  clearTsbDeviceSession,
  hasTsbDeviceSession,
  loadTsbDeviceSession,
  saveTsbDeviceSession
} from '@/utils/tsbDeviceSession'
import {
  initTabCoordinator,
  broadcastWsMessage,
  broadcastWsStatus,
  notifyLogout,
  destroyTabCoordinator
} from '@/utils/tsbWebSocketTab'
import {
  initTabRegistryLifecycle,
  registerDevice,
  refreshRegistryHeartbeat,
  stopHeartbeat,
  syncOpenedDevices,
  unregisterAllForCurrentTab,
  unregisterDevice
} from '@/utils/tsbDeviceTabRegistry'
import { ensureWorkspaceTag } from '@/utils/tsbWorkspaceNav'

/** deviceSn -> 连接池条目 */
const devicePool = new Map()

let manualCloseAll = false
let messageHandler = null
let tabCoordinatorInited = false
let wsRouterInited = false
let sessionInitialized = false
let currentDeviceSn = null
let userInitiatedConnect = false

const RECONNECT_DELAY = 3000
const MAX_RECONNECT_ATTEMPTS = 3

/** 同一设备连接中的 Promise，避免重复预检 */
const pendingConnects = new Map()

function createPoolEntry() {
  return {
    socket: null,
    connectId: 0,
    reconnectTimer: null,
    reconnectAttempts: 0,
    reconnectExhaustedNotified: false,
    manualClose: false
  }
}

function poolKey(deviceSn) {
  return String(deviceSn)
}

function getPoolEntry(deviceSn) {
  const key = poolKey(deviceSn)
  if (!devicePool.has(key)) {
    devicePool.set(key, createPoolEntry())
  }
  return devicePool.get(key)
}

function isEntryConnected(entry) {
  return !!(entry && entry.socket && entry.socket.readyState === WebSocket.OPEN)
}

function clearEntryReconnectTimer(entry) {
  if (entry.reconnectTimer) {
    clearTimeout(entry.reconnectTimer)
    entry.reconnectTimer = null
  }
}

function resetEntryReconnectAttempts(entry) {
  entry.reconnectAttempts = 0
  entry.reconnectExhaustedNotified = false
}

function getCurrentEntry() {
  return currentDeviceSn != null ? getPoolEntry(currentDeviceSn) : null
}

function broadcastWsStatusForDevice(deviceSn) {
  if (!isValidDeviceSn(deviceSn)) {
    return
  }
  const entry = getPoolEntry(deviceSn)
  broadcastWsStatus(isEntryConnected(entry), deviceSn)
}

function broadcastWsStatusForCurrent() {
  broadcastWsStatusForDevice(currentDeviceSn)
}

function ensureWsRouter() {
  if (wsRouterInited) {
    return
  }
  wsRouterInited = true
  initTsbWsRouter((handler) => onMessage(handler))
}

function ensureTabCoordinator() {
  if (tabCoordinatorInited) {
    return
  }
  tabCoordinatorInited = true
  initTabCoordinator({
    onTabLogout: () => disconnectAllDevices(false),
    onBroadcastMessage: (message) => dispatchBroadcastMessage(message)
  })
}

function buildWsUrl(token, deviceSn) {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  const base = process.env.VUE_APP_BASE_API || ''
  return `${protocol}//${window.location.host}${base}/websocket/tsb?token=${encodeURIComponent(token)}&deviceSn=${encodeURIComponent(deviceSn)}`
}

function extractErrorMessage(err) {
  if (!err) {
    return '预检失败'
  }
  if (typeof err === 'string') {
    return err
  }
  return err.msg || err.message || '预检失败'
}

function isValidDeviceSn(deviceSn) {
  return deviceSn != null && deviceSn !== ''
}

function isDeviceStillOpened(deviceSn) {
  return (store.getters.tsbOpenedDevices || []).some(
    d => String(d.deviceSn) === String(deviceSn)
  )
}

/** 无本会话：新浏览器页签从首页点卡片，须本页预检建连，禁止借用 Leader 已有连接 */
function isFreshTabConnect() {
  return !hasTsbDeviceSession()
}

function notifyPrecheckFailure(deviceSn, err, silent) {
  if (silent || poolKey(deviceSn) !== poolKey(currentDeviceSn)) {
    return
  }
  Message.error(extractErrorMessage(err))
}

function dispatchBroadcastMessage(message) {
  if (!messageHandler || !message) {
    return
  }
  messageHandler(message)
}

function persistDeviceSession(device) {
  if (!device || device.deviceSn == null) {
    return
  }
  const prev = loadTsbDeviceSession()
  const openedDevices = (prev && prev.openedDevices) || store.getters.tsbOpenedDevices || []
  const devicePanelMap = store.state.tsb.devicePanelMap || (prev && prev.devicePanelMap) || {}
  const session = buildDeviceSession(device, openedDevices, devicePanelMap)
  saveTsbDeviceSession(session)
  store.commit('tsb/SET_CURRENT_DEVICE', device)
  store.commit('tsb/ADD_OPENED_DEVICE', device)
  store.commit('tsb/SET_SESSION_CHECKED', true)
  registerDevice(device.deviceSn)
  refreshRegistryHeartbeat((store.getters.tsbOpenedDevices || []).map(d => d.deviceSn))
}

function restoreDeviceSession() {
  const session = loadTsbDeviceSession()
  if (!hasTsbDeviceSession()) {
    store.commit('tsb/SET_SESSION_CHECKED', false)
    return false
  }
  store.commit('tsb/RESTORE_SESSION', session)
  currentDeviceSn = session.currentDevice.deviceSn
  userInitiatedConnect = true
  return true
}

function runPrecheck(deviceSn, silent = true) {
  if (!isValidDeviceSn(deviceSn)) {
    return Promise.reject('请指定设备')
  }
  return getTsbWsBind(deviceSn, silent).then(res => res.data || {})
}

function bindSocketEvents(ws, entry, deviceSn) {
  const id = entry.connectId
  ws.onopen = () => {
    if (id !== entry.connectId) {
      return
    }
    clearEntryReconnectTimer(entry)
    broadcastWsStatusForDevice(deviceSn)
  }
  ws.onmessage = (evt) => {
    if (id !== entry.connectId || !messageHandler) {
      return
    }
    try {
      const message = JSON.parse(evt.data)
      const payload = { ...message, _tsbDeviceSn: deviceSn }
      broadcastWsMessage(payload)
      messageHandler(payload)
    } catch (e) {
      console.warn('TSB WebSocket 消息解析失败', e)
    }
  }
  ws.onclose = () => {
    if (id !== entry.connectId) {
      return
    }
    entry.socket = null
    broadcastWsStatusForDevice(deviceSn)
    if (!entry.manualClose && !manualCloseAll && userInitiatedConnect && getToken()
      && isDeviceStillOpened(deviceSn)) {
      scheduleReconnectForDevice(deviceSn)
    }
  }
  ws.onerror = () => {
    if (id !== entry.connectId) {
      return
    }
    ws.close()
  }
}

function leaderConnectDevice(deviceSn, resetAttempts = true) {
  ensureTabCoordinator()
  if (!userInitiatedConnect) {
    return
  }
  const token = getToken()
  if (!token || manualCloseAll) {
    return
  }
  const entry = getPoolEntry(deviceSn)
  if (isEntryConnected(entry) || (entry.socket && entry.socket.readyState === WebSocket.CONNECTING)) {
    return
  }
  clearEntryReconnectTimer(entry)
  if (resetAttempts !== false) {
    resetEntryReconnectAttempts(entry)
  }
  entry.manualClose = false
  entry.connectId++
  entry.socket = new WebSocket(buildWsUrl(token, deviceSn))
  bindSocketEvents(entry.socket, entry, deviceSn)
}

function applyPrecheckResultForDevice(deviceSn, bind, resetAttempts = true) {
  const entry = getPoolEntry(deviceSn)
  if (isEntryConnected(entry)) {
    clearEntryReconnectTimer(entry)
    if (resetAttempts !== false) {
      resetEntryReconnectAttempts(entry)
    }
    broadcastWsStatusForDevice(deviceSn)
    return bind
  }
  leaderConnectDevice(deviceSn, resetAttempts)
  return bind
}

function scheduleReconnectForDevice(deviceSn) {
  if (!isValidDeviceSn(deviceSn) || !isDeviceStillOpened(deviceSn)
    || !userInitiatedConnect || manualCloseAll || !getToken()) {
    return
  }
  const entry = getPoolEntry(deviceSn)
  if (entry.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
    if (!entry.reconnectExhaustedNotified) {
      entry.reconnectExhaustedNotified = true
    }
    broadcastWsStatusForDevice(deviceSn)
    return
  }
  entry.reconnectAttempts++
  clearEntryReconnectTimer(entry)
  entry.reconnectTimer = setTimeout(() => {
    if (!isDeviceStillOpened(deviceSn)) {
      return
    }
    runPrecheck(deviceSn, true).then((bind) => {
      applyPrecheckResultForDevice(deviceSn, bind, false)
    }).catch((err) => {
      clearEntryReconnectTimer(entry)
      entry.reconnectAttempts = MAX_RECONNECT_ATTEMPTS
      notifyPrecheckFailure(deviceSn, err, false)
      broadcastWsStatusForDevice(deviceSn)
    })
  }, RECONNECT_DELAY)
}

function ensureDeviceConnection(deviceSn, silent = true) {
  if (!isValidDeviceSn(deviceSn) || !userInitiatedConnect || manualCloseAll) {
    return Promise.resolve(false)
  }
  if (!isDeviceStillOpened(deviceSn)) {
    return Promise.resolve(false)
  }
  const entry = getPoolEntry(deviceSn)
  if (isEntryConnected(entry)) {
    broadcastWsStatusForDevice(deviceSn)
    return Promise.resolve(true)
  }
  if (!getToken()) {
    return Promise.resolve(false)
  }
  return runPrecheck(deviceSn, true).then((bind) => {
    applyPrecheckResultForDevice(deviceSn, bind, silent !== false)
    return true
  }).catch((err) => {
    notifyPrecheckFailure(deviceSn, err, silent)
    return false
  })
}

function closeDeviceConnection(deviceSn, removeFromPool = true) {
  const key = poolKey(deviceSn)
  const entry = devicePool.get(key)
  if (!entry) {
    return
  }
  entry.manualClose = true
  clearEntryReconnectTimer(entry)
  entry.connectId++
  if (entry.socket) {
    entry.socket.close()
    entry.socket = null
  }
  entry.manualClose = false
  if (removeFromPool) {
    devicePool.delete(key)
  }
  broadcastWsStatusForDevice(deviceSn)
}

function forceReconnectDevice(deviceSn) {
  if (!isValidDeviceSn(deviceSn)) {
    return Promise.reject('请先在首页选择设备')
  }
  const inPool = devicePool.has(poolKey(deviceSn))
  if (!isDeviceStillOpened(deviceSn) && !inPool) {
    return Promise.reject('设备已关闭，请重新选择')
  }
  closeDeviceConnection(deviceSn, false)
  const entry = getPoolEntry(deviceSn)
  resetEntryReconnectAttempts(entry)
  return runPrecheck(deviceSn, true).then((bind) => {
    applyPrecheckResultForDevice(deviceSn, bind, true)
    if (poolKey(deviceSn) === poolKey(currentDeviceSn)) {
      broadcastWsStatusForCurrent()
    }
    return bind
  }).catch((err) => Promise.reject(extractErrorMessage(err)))
}

function disconnectAllDevices(logout) {
  manualCloseAll = true
  devicePool.forEach((entry, sn) => {
    entry.manualClose = true
    clearEntryReconnectTimer(entry)
    entry.connectId++
    if (entry.socket) {
      entry.socket.close()
      entry.socket = null
    }
    entry.manualClose = false
  })
  const closedSns = [...devicePool.keys()]
  devicePool.clear()
  closedSns.forEach((sn) => broadcastWsStatus(false, sn))
  if (logout) {
    currentDeviceSn = null
    userInitiatedConnect = false
    sessionInitialized = false
    clearTsbDeviceSession()
    stopHeartbeat()
    unregisterAllForCurrentTab()
    notifyLogout()
    destroyTabCoordinator()
    tabCoordinatorInited = false
    wsRouterInited = false
  }
  manualCloseAll = false
}

function leaderSend(cmdType, data) {
  const entry = getCurrentEntry()
  if (!isEntryConnected(entry)) {
    return false
  }
  entry.socket.send(JSON.stringify({
    cmdType,
    data: data || {}
  }))
  return true
}

function connect() {
  ensureWsRouter()
  if (userInitiatedConnect && hasTsbDeviceSession()) {
    ensureTabCoordinator()
  }
}

function connectAfterLogin() {
  if (sessionInitialized) {
    return
  }
  sessionInitialized = true
  ensureWsRouter()
  if (!restoreDeviceSession()) {
    return
  }
  initTabRegistryLifecycle()
  syncOpenedDevices(store.getters.tsbOpenedDevices || [])
  ensureTabCoordinator()
  if (!getToken()) {
    return
  }
  const opened = store.getters.tsbOpenedDevices || []
  opened.forEach((device) => {
    if (device && isValidDeviceSn(device.deviceSn)) {
      ensureDeviceConnection(device.deviceSn, true)
    }
  })
  ensureWorkspaceTag()
}

function switchToDevice(device) {
  if (!device || !isValidDeviceSn(device.deviceSn)) {
    return Promise.reject('请指定设备')
  }
  const opened = store.getters.tsbOpenedDevices || []
  if (!opened.some(d => String(d.deviceSn) === String(device.deviceSn))) {
    return connectWithDevice(device)
  }
  ensureWsRouter()
  ensureTabCoordinator()
  currentDeviceSn = device.deviceSn
  userInitiatedConnect = true
  store.commit('tsb/SET_CURRENT_DEVICE', device)
  store.commit('tsb/NOTIFY_DEVICE_SWITCHED')
  const panelMap = store.state.tsb.devicePanelMap || {}
  saveTsbDeviceSession(buildDeviceSession(device, opened, panelMap))
  broadcastWsStatusForCurrent()
  const entry = getPoolEntry(device.deviceSn)
  if (isEntryConnected(entry)) {
    return Promise.resolve(device)
  }
  return ensureDeviceConnection(device.deviceSn, true).then(() => device)
}

function ensureCurrentDeviceConnection() {
  if (!userInitiatedConnect || !isValidDeviceSn(currentDeviceSn)) {
    return Promise.resolve(false)
  }
  return ensureDeviceConnection(currentDeviceSn, true)
}

function connectWithDevice(deviceOrSn) {
  const device = typeof deviceOrSn === 'object' ? deviceOrSn : { deviceSn: deviceOrSn }
  if (!isValidDeviceSn(device.deviceSn)) {
    return Promise.reject('请指定设备')
  }
  const key = poolKey(device.deviceSn)
  if (pendingConnects.has(key)) {
    return pendingConnects.get(key)
  }
  const task = doConnectWithDevice(device).finally(() => {
    pendingConnects.delete(key)
  })
  pendingConnects.set(key, task)
  return task
}

function doConnectWithDevice(device) {
  initTabRegistryLifecycle()

  ensureWsRouter()
  ensureTabCoordinator()

  const entry = getPoolEntry(device.deviceSn)
  if (isEntryConnected(entry) && !isFreshTabConnect()) {
    persistDeviceSession(device)
    currentDeviceSn = device.deviceSn
    userInitiatedConnect = true
    broadcastWsStatusForCurrent()
    return Promise.resolve(device)
  }

  // 静默预检，错误由调用方统一提示一次（避免与 axios 拦截器重复弹窗）
  return runPrecheck(device.deviceSn, true).then((bind) => {
    persistDeviceSession(device)
    currentDeviceSn = device.deviceSn
    userInitiatedConnect = true
    applyPrecheckResultForDevice(device.deviceSn, bind, true)
    broadcastWsStatusForCurrent()
    return bind
  }).catch((err) => Promise.reject(extractErrorMessage(err)))
}

function disconnect(logout) {
  ensureTabCoordinator()
  disconnectAllDevices(logout)
}

function reconnect() {
  if (!userInitiatedConnect || !isValidDeviceSn(currentDeviceSn)) {
    return Promise.reject('请先在首页选择设备')
  }
  if (!isDeviceStillOpened(currentDeviceSn)) {
    return Promise.reject('设备已关闭，请重新选择')
  }
  ensureTabCoordinator()
  return forceReconnectDevice(currentDeviceSn)
}

function isReconnectExhausted() {
  const entry = getCurrentEntry()
  return entry ? entry.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS : false
}

function notifyConnectSuccess() {
  const entry = getCurrentEntry()
  if (entry) {
    resetEntryReconnectAttempts(entry)
  }
  broadcastWsStatusForCurrent()
}

function notifyConnectFailed() {
  // 鉴权失败等场景由 onclose 触发 scheduleReconnectForDevice
}

function send(cmdType, data) {
  if (!hasSelectedDevice()) {
    return false
  }
  ensureTabCoordinator()
  return leaderSend(cmdType, data)
}

function sendPageSync(cmdType, data) {
  return send(cmdType, data)
}

function onMessage(handler) {
  messageHandler = handler
}

function isConnected() {
  return isEntryConnected(getCurrentEntry())
}

function getReconnectAttempts() {
  const entry = getCurrentEntry()
  return entry ? entry.reconnectAttempts : 0
}

function getCurrentDeviceSn() {
  return currentDeviceSn
}

function hasSelectedDevice() {
  return userInitiatedConnect && currentDeviceSn != null
}

function releaseWorkspaceSession() {
  const sn = currentDeviceSn
  currentDeviceSn = null
  userInitiatedConnect = false
  stopHeartbeat()
  unregisterAllForCurrentTab()
  if (isValidDeviceSn(sn)) {
    broadcastWsStatus(false, sn)
  }
}

function onDeviceTabClosed(deviceSn) {
  unregisterDevice(deviceSn)
  refreshRegistryHeartbeat((store.getters.tsbOpenedDevices || []).map(d => d.deviceSn))
}

export default {
  connect,
  connectAfterLogin,
  connectWithDevice,
  switchToDevice,
  ensureCurrentDeviceConnection,
  disconnect,
  disconnectDevice: closeDeviceConnection,
  reconnect,
  send,
  sendPageSync,
  onMessage,
  isConnected,
  isReconnectExhausted,
  getReconnectAttempts,
  getCurrentDeviceSn,
  hasSelectedDevice,
  releaseWorkspaceSession,
  onDeviceTabClosed,
  notifyConnectSuccess,
  notifyConnectFailed
}
