/** WebRTC/Mediasoup client utilities. */
import { logError, logWarn } from '../utils/logger.js'

const FETCH_OPTS = { credentials: 'include' }

export async function createMediasoupDevice(rtpCapabilities) {
  if (typeof window === 'undefined') throw new TypeError('Mediasoup device can only be created in browser')
  const { Device } = await import('mediasoup-client')
  const device = new Device()
  await device.load({ routerRtpCapabilities: rtpCapabilities })
  return device
}

export function createWebSocketConnection(url) {
  return new Promise((resolve, reject) => {
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const wsUrl = url.startsWith('ws') ? url : `${protocol}//${window.location.host}/ws`
    const ws = new WebSocket(wsUrl)
    ws.onopen = () => resolve(ws)
    ws.onerror = () => reject(new Error('WebSocket connection failed'))
  })
}

export function sendWebSocketMessage(ws, sessionId, type, data = {}) {
  return new Promise((resolve, reject) => {
    if (ws.readyState !== WebSocket.OPEN) {
      reject(new Error('WebSocket not open'))
      return
    }

    const messageId = `${Date.now()}-${Math.random()}`
    const message = { sessionId, type, data, messageId }

    const timeout = setTimeout(() => {
      ws.removeEventListener('message', handler)
      reject(new Error('WebSocket message timeout'))
    }, 10000)

    const handler = (event) => {
      try {
        const response = JSON.parse(event.data)
        if (response.messageId === messageId || response.type) {
          clearTimeout(timeout)
          ws.removeEventListener('message', handler)
          if (response.error) {
            reject(new Error(response.error))
          }
          else {
            resolve(response)
          }
        }
      }
      catch {
        // Not our message, continue waiting
      }
    }

    ws.addEventListener('message', handler)
    ws.send(JSON.stringify(message))
  })
}

function attachTransportHandlers(transport, transportParams, sessionId, label, { onConnectSuccess, onConnectFailure } = {}) {
  transport.on('connect', async ({ dtlsParameters }, callback, errback) => {
    try {
      await $fetch('/api/live/webrtc/connect-transport', {
        method: 'POST',
        body: { sessionId, transportId: transportParams.id, dtlsParameters },
        ...FETCH_OPTS,
      })
      onConnectSuccess?.()
      callback()
    }
    catch (err) {
      logError(`useWebRTC: ${label} transport connect failed`, {
        err: err?.message ?? String(err),
        transportId: transportParams.id,
        connectionState: transport.connectionState,
        sessionId,
      })
      onConnectFailure?.(err)
      errback(err)
    }
  })
  transport.on('connectionstatechange', () => {
    const state = transport.connectionState
    if (['failed', 'disconnected', 'closed'].includes(state)) {
      logWarn(`useWebRTC: ${label} transport connection state changed`, { state, transportId: transportParams.id, sessionId })
    }
  })
}

export async function createSendTransport(device, sessionId, options = {}) {
  const transportParams = await $fetch('/api/live/webrtc/create-transport', {
    method: 'POST',
    body: { sessionId, isProducer: true },
    ...FETCH_OPTS,
  })
  const transport = device.createSendTransport({
    id: transportParams.id,
    iceParameters: transportParams.iceParameters,
    iceCandidates: transportParams.iceCandidates,
    dtlsParameters: transportParams.dtlsParameters,
  })
  attachTransportHandlers(transport, transportParams, sessionId, 'Send', options)

  transport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
    try {
      const { id } = await $fetch('/api/live/webrtc/create-producer', {
        method: 'POST',
        body: { sessionId, transportId: transportParams.id, kind, rtpParameters },
        ...FETCH_OPTS,
      })
      callback({ id })
    }
    catch (err) {
      logError('useWebRTC: Producer creation failed', { err: err?.message ?? String(err) })
      errback(err)
    }
  })
  return transport
}

export async function createRecvTransport(device, sessionId) {
  const transportParams = await $fetch('/api/live/webrtc/create-transport', {
    method: 'POST',
    body: { sessionId, isProducer: false },
    ...FETCH_OPTS,
  })
  const transport = device.createRecvTransport({
    id: transportParams.id,
    iceParameters: transportParams.iceParameters,
    iceCandidates: transportParams.iceCandidates,
    dtlsParameters: transportParams.dtlsParameters,
  })
  attachTransportHandlers(transport, transportParams, sessionId, 'Recv')
  return transport
}

export async function consumeProducer(transport, device, sessionId) {
  const consumerParams = await $fetch('/api/live/webrtc/create-consumer', {
    method: 'POST',
    body: { sessionId, transportId: transport.id, rtpCapabilities: device.rtpCapabilities },
    ...FETCH_OPTS,
  })

  const consumer = await transport.consume({
    id: consumerParams.id,
    producerId: consumerParams.producerId,
    kind: consumerParams.kind,
    rtpParameters: consumerParams.rtpParameters,
  })

  if (!consumer.track) {
    logWarn('useWebRTC: Consumer created but no track immediately', { consumerId: consumer.id })
    await waitForCondition(() => consumer.track, 3000, 100)
    if (!consumer.track) {
      logError('useWebRTC: Track did not become available after 3s', { consumerId: consumer.id })
    }
  }

  return consumer
}

function waitForCondition(condition, timeoutMs = 3000, intervalMs = 100) {
  return new Promise((resolve) => {
    const timeoutId = setTimeout(() => {
      clearInterval(intervalId)
      resolve()
    }, timeoutMs)
    const intervalId = setInterval(() => {
      if (condition()) {
        clearTimeout(timeoutId)
        clearInterval(intervalId)
        resolve()
      }
    }, intervalMs)
    if (condition()) {
      clearTimeout(timeoutId)
      clearInterval(intervalId)
      resolve()
    }
  })
}

export function waitForConnectionState(transport, timeoutMs = 10000) {
  const terminal = ['connected', 'failed', 'disconnected', 'closed']
  return new Promise((resolve) => {
    const tid = ref(null)
    const handler = () => {
      const state = transport.connectionState
      if (terminal.includes(state)) {
        transport.off('connectionstatechange', handler)
        if (tid.value) clearTimeout(tid.value)
        resolve(state)
      }
    }
    transport.on('connectionstatechange', handler)
    handler()
    tid.value = setTimeout(() => {
      transport.off('connectionstatechange', handler)
      resolve(transport.connectionState)
    }, timeoutMs)
  })
}
