import { getToken } from '@/utils/auth'

let ws = null
let reconnectTimer = null
let manualClose = false
let retryCount = 0
const subscribers = new Set()

const WS_PATH = '/ws'
const MAX_RECONNECT_DELAY = 30000

function getWsBaseUrl() {
  const baseApi = import.meta.env.VITE_APP_BASE_API || ''

  if (/^https?:\/\//.test(baseApi)) {
    return baseApi.replace(/^http/, 'ws').replace(/\/$/, '')
  }

  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  return `${protocol}//${window.location.host}${baseApi}`.replace(/\/$/, '')
}

function buildWsUrl() {
  const url = new URL(`${getWsBaseUrl()}${WS_PATH}`)
  const token = getToken()

  if (token) {
    url.searchParams.set('token', token)
  }

  return url.toString()
}

export function connectWs(onMessage) {
  console.trace('connectWs called')
  if (!getToken()) {
    console.warn('[WebSocket] missing token')
    return
  }

  if (typeof onMessage === 'function') {
    subscribers.add(onMessage)
  }

  manualClose = false
  clearReconnectTimer()

  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
    return
  }

  ws = new WebSocket(buildWsUrl())

  ws.onopen = () => {
    retryCount = 0
    console.log('[WebSocket] connected')
  }

  ws.onmessage = (event) => {
    let data = event.data

    try {
      data = JSON.parse(event.data)
    } catch (e) {
      // Keep plain text messages as-is.
    }

    subscribers.forEach((handler) => {
      try {
        handler(data, event)
      } catch (error) {
        console.error('[WebSocket] subscriber error', error)
      }
    })
  }

  ws.onclose = (event) => {
    console.log('[WebSocket] closed', { code: event.code, reason: event.reason })
    ws = null

    if (isNonRetryableClose(event)) {
      console.warn('[WebSocket] stop reconnect because close reason is not retryable', {
        code: event.code,
        reason: event.reason
      })
      retryCount = 0
      clearReconnectTimer()
      return
    }

    if (!manualClose && subscribers.size > 0) {
      reconnectTimer = setTimeout(() => {
        console.log('[WebSocket] reconnecting')
        connectWs()
      }, getReconnectDelay())
    }
  }

  ws.onerror = (error) => {
    console.error('[WebSocket] error', error)
  }
}

export function closeWs(onMessage) {
  if (typeof onMessage === 'function') {
    subscribers.delete(onMessage)
  } else {
    subscribers.clear()
  }

  if (subscribers.size > 0) {
    return
  }

  manualClose = true
  retryCount = 0
  clearReconnectTimer()
  if (ws) {
    ws.close()
    ws = null
  }
}

export function reconnectWs(onMessage) {
  if (typeof onMessage === 'function') {
    subscribers.add(onMessage)
  }

  manualClose = false
  retryCount = 0
  clearReconnectTimer()

  if (ws) {
    ws.close()
    ws = null
  }

  manualClose = false
  connectWs()
}

export function sendWs(data) {
  if (!ws || ws.readyState !== WebSocket.OPEN) {
    console.warn('[WebSocket] send failed, socket is not open')
    return false
  }

  ws.send(typeof data === 'string' ? data : JSON.stringify(data))
  return true
}

export function getWsState() {
  return ws ? ws.readyState : WebSocket.CLOSED
}

function getReconnectDelay() {
  retryCount += 1
  return Math.min(5000 * retryCount, MAX_RECONNECT_DELAY)
}

function clearReconnectTimer() {
  if (reconnectTimer) {
    clearTimeout(reconnectTimer)
    reconnectTimer = null
  }
}

function isNonRetryableClose(event) {
  const reason = (event?.reason || '').toLowerCase()
  return (
    reason.includes('invalid token') ||
    reason.includes('missing token') ||
    reason.includes('deptid is null')
  )
}
