'use strict'

const HOST_FIELDS = ['HostName', 'IPAddress', 'PhysAddress', 'Active', 'AddressSource', 'LeaseTimeRemaining', 'Layer1Interface']
const hostParams = ['Device.Hosts.HostNumberOfEntries']
for (let i = 1; i <= 20; i++) for (const f of HOST_FIELDS) hostParams.push(`Device.Hosts.Host.${i}.${f}`)

const multiApParams = []
for (let d = 1; d <= 3; d++) {
  for (let r = 1; r <= 2; r++) {
    for (let a = 1; a <= 4; a++) {
      multiApParams.push(`Device.WiFi.MultiAP.APDevice.${d}.Radio.${r}.AP.${a}.AssociatedDeviceNumberOfEntries`)
      for (let c = 1; c <= 8; c++) {
        const base = `Device.WiFi.MultiAP.APDevice.${d}.Radio.${r}.AP.${a}.AssociatedDevice.${c}`
        multiApParams.push(`${base}.MACAddress`, `${base}.SignalStrength`, `${base}.X_TP_NegotiationSpeed`)
      }
    }
  }
  const base = `Device.WiFi.MultiAP.APDevice.${d}`
  multiApParams.push(`${base}.X_TP_HostName`, `${base}.MACAddress`, `${base}.X_TP_Active`, `${base}.Radio.1.OperatingFrequencyBand`, `${base}.Radio.2.OperatingFrequencyBand`)
}

module.exports = function createHostsHandler ({ nbiRequest, json, deviceCache, cacheHosts, getCachedHosts }) {
  return async function handleHosts (res, deviceId) {
    const encId = encodeURIComponent(deviceId)

    try { await nbiRequest(`/devices/${encId}/tasks?connection_request&timeout=15000`, 'POST', { name: 'getParameterValues', parameterNames: hostParams }) } catch {}
    try { await nbiRequest(`/devices/${encId}/tasks?timeout=10000`, 'POST', { name: 'getParameterValues', parameterNames: multiApParams }) } catch {}

    const result = await nbiRequest(`/devices/?query=${encodeURIComponent(JSON.stringify({ _id: deviceId }))}&projection=Device.Hosts,Device.WiFi.MultiAP`)
    const devices = Array.isArray(result.data) ? result.data : []
    if (!devices.length) return json(res, 404, { error: 'Device not found' })

    const dd = devices[0]
    const hostTree = dd.Device?.Hosts?.Host || {}
    const hostCount = dd.Device?.Hosts?.HostNumberOfEntries?._value || 0

    // Build client MAC -> node mapping from MultiAP
    const clientNodeMap = new Map(), meshNodes = new Map()
    const apDevices = dd.Device?.WiFi?.MultiAP?.APDevice || {}
    for (const dk of Object.keys(apDevices)) {
      if (dk.startsWith('_')) continue
      const dev = apDevices[dk]
      const nodeMac = dev.MACAddress?._value || '', nodeName = dev.X_TP_HostName?._value || ''
      if (nodeMac) meshNodes.set(nodeMac.toUpperCase(), { name: nodeName, ip: dev.X_TP_IPAddress?._value || '' })
      for (const rk of Object.keys(dev.Radio || {})) {
        if (rk.startsWith('_')) continue
        const radio = dev.Radio[rk]
        if (!radio) continue
        const freqBand = radio.OperatingFrequencyBand?._value || ''
        const band = freqBand.includes('5') ? '5GHz' : freqBand.includes('2.4') ? '2.4GHz' : freqBand || ''
        for (const ak of Object.keys(radio.AP || {})) {
          if (ak.startsWith('_')) continue
          for (const ck of Object.keys(radio.AP[ak]?.AssociatedDevice || {})) {
            if (ck.startsWith('_')) continue
            const client = radio.AP[ak].AssociatedDevice[ck]
            const cMac = client?.MACAddress?._value
            if (!cMac) continue
            clientNodeMap.set(cMac.toUpperCase(), {
              nodeName, nodeMac: nodeMac.toUpperCase(), band,
              signal: client.SignalStrength?._value != null ? Number(client.SignalStrength._value) : null,
              speed: client.X_TP_NegotiationSpeed?._value != null ? String(client.X_TP_NegotiationSpeed._value) : '',
            })
          }
        }
      }
    }

    const hosts = []
    for (const k of Object.keys(hostTree)) {
      if (k.startsWith('_')) continue
      const h = hostTree[k]
      if (!h?._object === undefined) continue
      const gv = key => h[key]?._value !== undefined ? h[key]._value : null
      const name = gv('HostName'), ip = gv('IPAddress'), mac = gv('PhysAddress')
      if (!name && !ip && !mac) continue
      const isMeshNode = mac && meshNodes.has(mac.toUpperCase())
      const nodeInfo = mac ? clientNodeMap.get(mac.toUpperCase()) : null
      const connType = isMeshNode ? 'mesh-node' : (!nodeInfo && !gv('Active')) ? 'unknown' : 'wifi'
      hosts.push({
        id: k, name: name || '', ip: ip || '', mac: mac || '',
        active: gv('Active') === true, addressSource: gv('AddressSource') || '',
        leaseRemaining: gv('LeaseTimeRemaining') != null ? Number(gv('LeaseTimeRemaining')) : null,
        connType, band: nodeInfo?.band || '', signal: nodeInfo?.signal ?? null,
        linkRate: nodeInfo?.speed || '', attachedNode: nodeInfo?.nodeName || '',
        attachedMac: nodeInfo?.nodeMac || '', isMeshNode,
      })
    }
    hosts.sort((a, b) => {
      if (a.isMeshNode !== b.isMeshNode) return a.isMeshNode ? 1 : -1
      if (a.active !== b.active) return a.active ? -1 : 1
      return (a.name || a.ip).localeCompare(b.name || b.ip)
    })

    const hostsResult = { total: hostCount, hosts }
    for (const [serial, entry] of deviceCache) {
      if (entry.summary?._id === deviceId) { cacheHosts(serial, hostsResult); break }
    }

    return json(res, 200, hostsResult)
  }
}
