import { apiBase } from '../config'

/** WebSocket 信令（观众） */
export function liveWsURLView() {
  const path = '/api/web/live/ws?role=view'
  if (apiBase) {
    const base = apiBase.replace(/\/$/, '')
    const wsOrigin = base.replace(/^https:/i, 'wss:').replace(/^http:/i, 'ws:')
    return `${wsOrigin}${path}`
  }
  const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  return `${proto}//${window.location.host}${path}`
}

export function liveInfoURL() {
  return apiBase ? `${apiBase}/api/web/live/info` : '/api/web/live/info'
}

/** @param {string} [token] 前台弹幕 JWT，缺省则仅收广播不可发 */
export function liveDanmakuWsURL(token) {
  const q = token ? `?token=${encodeURIComponent(token)}` : ''
  const path = `/api/web/live/danmaku/ws${q}`
  if (apiBase) {
    const base = apiBase.replace(/\/$/, '')
    const wsOrigin = base.replace(/^https:/i, 'wss:').replace(/^http:/i, 'ws:')
    return `${wsOrigin}${path}`
  }
  const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  return `${proto}//${window.location.host}${path}`
}

export async function fetchLiveStatus() {
  const url = apiBase ? `${apiBase}/api/web/live/status` : '/api/web/live/status'
  try {
    const res = await fetch(url)
    if (!res.ok) return { live: false, upstreamError: true }
    const j = await res.json()
    return { live: Boolean(j.live), upstreamError: false }
  } catch {
    return { live: false, upstreamError: true }
  }
}

const defaultIce = [{ urls: 'stun:stun.l.google.com:19302' }]

/**
 * 观众端：轮询 live 后拉流；单路视频 + 音频（主播端 Canvas 合成）
 * @param {HTMLVideoElement | null} videoEl
 * @param {{ onStatus?: function, onLive?: function, onEnded?: function, pollMs?: number, muted?: boolean }} [opts]
 */
export function startViewing(videoEl, opts = {}) {
  const { onStatus = () => {}, onLive = () => {}, onEnded = () => {}, pollMs = 1200, muted = true } =
    opts

  let mainRecv = new MediaStream()

  function kickPlay() {
    if (stopped || !videoEl) return
    const run = () => {
      if (stopped || !videoEl) return
      const p = videoEl.play()
      if (p && typeof p.catch === 'function') p.catch(() => {})
    }
    run()
    requestAnimationFrame(run)
  }

  function syncMain() {
    if (!videoEl) return
    videoEl.srcObject = mainRecv
    videoEl.muted = !!muted
    kickPlay()
  }

  function handleTrack(e) {
    if (e.track.kind === 'audio') {
      mainRecv.addTrack(e.track)
      syncMain()
      return
    }
    if (e.track.kind === 'video') {
      mainRecv.getVideoTracks().forEach((t) => {
        try {
          mainRecv.removeTrack(t)
        } catch (_) {}
      })
      mainRecv.addTrack(e.track)
      syncMain()
    }
  }

  function newPeer() {
    const pc = new RTCPeerConnection({ iceServers: defaultIce })
    pc.ontrack = handleTrack
    return pc
  }

  let pc = newPeer()
  let ws = null
  let pollTimer = null
  let currentPollMs = pollMs
  let pollInFlight = false
  let upstreamErrorStreak = 0
  let stopped = false
  let blackFrameTimer = null

  function schedulePollLoop() {
    if (pollTimer) clearInterval(pollTimer)
    pollTimer = null
    if (stopped) return
    pollTimer = setInterval(poll, currentPollMs)
  }

  function clearBlackFrameTimer() {
    if (blackFrameTimer) {
      clearTimeout(blackFrameTimer)
      blackFrameTimer = null
    }
  }

  function attachIceDiagnostics(peer) {
    peer.onconnectionstatechange = () => {
      if (stopped) return
      if (peer.connectionState === 'failed') {
        onStatus('播放连接失败')
      }
    }
  }

  function scheduleBlackFrameHint(peer) {
    clearBlackFrameTimer()
    blackFrameTimer = setTimeout(() => {
      blackFrameTimer = null
      if (stopped || !videoEl) return
      const hasStream = Boolean(videoEl.srcObject)
      const noFrame = videoEl.videoWidth === 0
      if (hasStream && noFrame && peer.connectionState === 'connected') {
        onStatus('暂无画面')
      }
    }, 6000)
  }

  function rebuildPeer() {
    try {
      pc.close()
    } catch (_) {}
    mainRecv = new MediaStream()
    pc = newPeer()
  }

  function resumePollingAfterDisconnect(tip) {
    if (stopped) return
    clearBlackFrameTimer()
    if (videoEl) videoEl.srcObject = null
    if (tip) onStatus(tip)
    if (pollTimer) return
    rebuildPeer()
    currentPollMs = pollMs
    upstreamErrorStreak = 0
    schedulePollLoop()
    setTimeout(() => {
      if (!stopped) poll()
    }, 0)
  }

  async function negotiate() {
    if (stopped) return
    const icePending = []
    pc.onicecandidate = (e) => {
      if (!e.candidate) return
      const msg = JSON.stringify({ type: 'ice', candidate: e.candidate.toJSON() })
      if (ws && ws.readyState === WebSocket.OPEN) ws.send(msg)
      else icePending.push(msg)
    }
    pc.addTransceiver('video', { direction: 'recvonly' })
    pc.addTransceiver('audio', { direction: 'recvonly' })
    const offer = await pc.createOffer()
    await pc.setLocalDescription(offer)
    ws = new WebSocket(liveWsURLView())
    await new Promise((resolve, reject) => {
      ws.addEventListener('open', () => resolve(), { once: true })
      ws.addEventListener('error', () => reject(new Error('WebSocket 连接失败')), { once: true })
    })
    ws.send(JSON.stringify({ type: 'offer', sdp: offer.sdp }))
    while (icePending.length) {
      ws.send(icePending.shift())
    }
    attachIceDiagnostics(pc)
    ws.onmessage = async (ev) => {
      let msg
      try {
        msg = JSON.parse(ev.data)
      } catch {
        return
      }
      if (msg.type === 'answer' && msg.sdp) {
        await pc.setRemoteDescription({ type: 'answer', sdp: msg.sdp })
        onStatus('正在播放…')
        scheduleBlackFrameHint(pc)
        syncMain()
        ;[300, 1200, 2800, 5200].forEach((ms) => {
          setTimeout(() => kickPlay(), ms)
        })
      }
      if (msg.type === 'ice' && msg.candidate) {
        try {
          await pc.addIceCandidate(msg.candidate)
        } catch (_) {}
      }
      if (msg.type === 'ended') {
        onEnded()
        try {
          ws?.close()
        } catch (_) {}
        ws = null
        resumePollingAfterDisconnect(msg.message || '直播已结束')
      }
      if (msg.type === 'error') {
        onStatus(msg.message || '错误')
      }
    }
    ws.onclose = () => {
      if (stopped) return
      ws = null
      resumePollingAfterDisconnect('连接已断开，正在尝试重连…')
    }
  }

  async function poll() {
    if (stopped) return
    if (pollInFlight) return
    pollInFlight = true
    try {
      const { live, upstreamError } = await fetchLiveStatus()
      if (upstreamError) {
        upstreamErrorStreak += 1
        const extra =
          upstreamErrorStreak >= 6 ? ' 请稍后再试或刷新页面。' : ''
        onStatus(`无法连接服务器，将放慢重试…${extra}`)
        const next = Math.min(Math.round(currentPollMs * 1.5), 30000)
        if (next !== currentPollMs) {
          currentPollMs = next
          schedulePollLoop()
        }
        return
      }
      upstreamErrorStreak = 0
      if (currentPollMs !== pollMs) {
        currentPollMs = pollMs
        schedulePollLoop()
      }
      if (live) {
        if (pollTimer) {
          clearInterval(pollTimer)
          pollTimer = null
        }
        onLive()
        onStatus('连接中…')
        try {
          await negotiate()
        } catch (err) {
          onStatus(err?.message ? `连接失败：${err.message}` : '连接失败，将恢复轮询…')
          resumePollingAfterDisconnect('')
        }
        return
      }
      onStatus('等待开播…')
    } catch {
      onStatus('无法获取直播状态')
    } finally {
      pollInFlight = false
    }
  }

  currentPollMs = pollMs
  poll()
  schedulePollLoop()

  function stop() {
    stopped = true
    clearBlackFrameTimer()
    if (pollTimer) clearInterval(pollTimer)
    pollTimer = null
    try {
      ws?.close()
    } catch (_) {}
    ws = null
    pc.close()
    if (videoEl) videoEl.srcObject = null
  }

  return { stop, nudgePlay: kickPlay }
}
