/**
 * GenieACS live device status composable.
 * Looks up devices by serial number via targo-hub → GenieACS NBI proxy.
 */
import { ref, readonly } from 'vue'

import { HUB_URL } from 'src/config/hub'

// Singleton state — survives component remounts so cached data shows instantly
const cache = new Map()        // serial → { data, ts }
const hostsCache = new Map()   // serial → { data, ts }
const oltCache = new Map()     // serial → { data, ts }
const deviceMap = ref(new Map()) // serial → { ...summarizedDevice }
const oltMap = ref(new Map())    // serial → { status, rxPowerOlt, ... }
const loading = ref(false)
const error = ref(null)
const CACHE_TTL = 60_000 // 1 min
const HOSTS_TTL = 120_000 // 2 min
const OLT_TTL = 60_000 // 1 min

/**
 * Fetch live device info from GenieACS for a list of equipment objects.
 * Equipment must have serial_number (and optionally mac_address).
 * Returns a reactive Map<serial, deviceInfo>.
 */
export function useDeviceStatus () {

  async function fetchStatus (equipmentList) {
    if (!equipmentList || !equipmentList.length) return
    error.value = null

    const now = Date.now()
    const toFetch = []
    const map = new Map(deviceMap.value)

    // Immediately populate from cache so UI shows buffered data
    for (const eq of equipmentList) {
      const serial = eq.serial_number
      if (!serial) continue
      const cached = cache.get(serial)
      if (cached) {
        // Always show cached data immediately (even if stale)
        map.set(serial, cached.data)
        // Only skip re-fetch if cache is fresh
        if ((now - cached.ts) < CACHE_TTL) continue
      }
      toFetch.push(serial)
    }

    // Apply cached data right away before network fetch
    if (map.size > deviceMap.value.size) {
      deviceMap.value = new Map(map)
    }

    if (!toFetch.length) return

    // Batch lookups (GenieACS NBI doesn't support batch, so parallel individual)
    loading.value = true
    // Build per-serial hints from equipment records (OLT coords for Raisecom RCMG lookup)
    const hintsBySerial = new Map()
    for (const eq of equipmentList) {
      if (!eq.serial_number) continue
      const hints = {}
      if (eq.ip_address) hints.ip = eq.ip_address
      if (eq.olt_ip) hints.olt_ip = eq.olt_ip
      if (eq.olt_slot) hints.olt_slot = eq.olt_slot
      if (eq.olt_port) hints.olt_port = eq.olt_port
      if (eq.olt_ontid) hints.olt_ontid = eq.olt_ontid
      if (Object.keys(hints).length) hintsBySerial.set(eq.serial_number, hints)
    }
    const results = await Promise.allSettled(
      toFetch.map(serial => {
        const params = new URLSearchParams({ serial })
        const hints = hintsBySerial.get(serial) || {}
        for (const [k, v] of Object.entries(hints)) params.set(k, v)
        return fetch(`${HUB_URL}/devices/lookup?${params}`)
          .then(r => r.ok ? r.json() : null)
          .then(data => ({ serial, data: Array.isArray(data) && data.length ? data[0] : null }))
          .catch(() => ({ serial, data: null }))
      })
    )
    for (const r of results) {
      if (r.status === 'fulfilled' && r.value.data) {
        // Merge fresh data on top of existing cached data (don't clear fields)
        const existing = map.get(r.value.serial)
        const fresh = r.value.data
        const merged = existing ? { ...existing, ...fresh } : fresh
        map.set(r.value.serial, merged)
        cache.set(r.value.serial, { data: merged, ts: now })
      }
    }

    deviceMap.value = map
    loading.value = false
  }

  function getDevice (serial) {
    return deviceMap.value.get(serial) || null
  }

  /**
   * Fetch OLT SNMP status for a serial number.
   * Returns { status: 'online'|'offline', rxPowerOlt, distance, ... } or null.
   */
  async function fetchOltStatus (serial) {
    if (!serial) return null
    const now = Date.now()
    const cached = oltCache.get(serial)
    if (cached && (now - cached.ts) < OLT_TTL) return cached.data

    try {
      const res = await fetch(`${HUB_URL}/olt/onus?serial=${encodeURIComponent(serial)}`)
      if (!res.ok) return cached?.data || null
      const data = await res.json()
      if (data && !data.error) {
        oltCache.set(serial, { data, ts: now })
        const map = new Map(oltMap.value)
        map.set(serial, data)
        oltMap.value = map
        return data
      }
    } catch {}
    return cached?.data || null
  }

  function getOltData (serial) {
    return oltMap.value.get(serial) || null
  }

  function isOnline (serial) {
    const d = getDevice(serial)
    const tr069Online = d && d.lastInform
      ? (Date.now() - new Date(d.lastInform).getTime()) < 15 * 60 * 1000
      : null

    // Cross-reference OLT SNMP — ground truth for fiber connectivity
    const olt = getOltData(serial)
    const oltOnline = olt ? olt.status === 'online' : null

    // OLT is authoritative: if OLT says online, device has internet regardless of TR-069
    if (oltOnline === true) return true
    if (oltOnline === false) return false
    // No OLT data available — fall back to TR-069 only
    return tr069Online
  }

  /**
   * Combined status with source info for UI display.
   * Returns { online: bool|null, source: 'both'|'olt'|'tr069'|'unknown', label: string, detail: string }
   */
  function combinedStatus (serial) {
    const d = getDevice(serial)
    const tr069Online = d && d.lastInform
      ? (Date.now() - new Date(d.lastInform).getTime()) < 15 * 60 * 1000
      : null
    const olt = getOltData(serial)
    const oltOnline = olt ? olt.status === 'online' : null

    // Both agree online
    if (tr069Online === true && oltOnline === true) {
      return { online: true, source: 'both', label: 'En ligne', detail: 'TR-069 + Fibre OK' }
    }
    // OLT says online, TR-069 stale — device is online, management channel stale
    if (oltOnline === true && tr069Online !== true) {
      return { online: true, source: 'olt', label: 'En ligne', detail: 'Fibre OK · TR-069 inactif' }
    }
    // Both agree offline
    if (tr069Online === false && oltOnline === false) {
      return { online: false, source: 'both', label: 'Hors ligne', detail: 'TR-069 + Fibre hors ligne' }
    }
    // OLT says offline, TR-069 unknown
    if (oltOnline === false && tr069Online === null) {
      return { online: false, source: 'olt', label: 'Hors ligne', detail: 'Fibre hors ligne' }
    }
    // OLT says offline, TR-069 says online (unlikely but possible during transition)
    if (oltOnline === false && tr069Online === true) {
      return { online: false, source: 'olt', label: 'Hors ligne', detail: 'Fibre coupée · TR-069 résiduel' }
    }
    // No OLT data, TR-069 only
    if (tr069Online === true) {
      return { online: true, source: 'tr069', label: 'En ligne', detail: 'TR-069 actif' }
    }
    if (tr069Online === false) {
      return { online: false, source: 'tr069', label: 'Hors ligne', detail: 'TR-069 inactif · OLT non vérifié' }
    }
    return { online: null, source: 'unknown', label: 'Inconnu', detail: 'Aucune donnée' }
  }

  function signalQuality (serial) {
    const d = getDevice(serial)
    if (!d || d.rxPower == null) return null
    const rx = parseFloat(d.rxPower)
    if (isNaN(rx)) return null
    // GPON Rx power: > -8 excellent, -8 to -20 good, -20 to -25 fair, < -25 bad
    if (rx > -8) return 'excellent'
    if (rx > -20) return 'good'
    if (rx > -25) return 'fair'
    return 'bad'
  }

  async function rebootDevice (serial) {
    const d = getDevice(serial)
    if (!d) throw new Error('Device not found in ACS')
    const res = await fetch(
      `${HUB_URL}/devices/${encodeURIComponent(d._id)}/tasks?connection_request&timeout=5000`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: 'reboot' }),
      }
    )
    if (!res.ok) {
      const err = await res.json().catch(() => ({}))
      throw new Error(err.error || 'Reboot failed')
    }
    return res.json()
  }

  async function refreshDeviceParams (serial) {
    const d = getDevice(serial)
    if (!d) throw new Error('Device not found in ACS')
    const res = await fetch(
      `${HUB_URL}/devices/${encodeURIComponent(d._id)}/tasks?connection_request&timeout=10000`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: 'getParameterValues',
          parameterNames: [
            'InternetGatewayDevice.DeviceInfo.',
            'InternetGatewayDevice.WANDevice.1.',
            'Device.DeviceInfo.',
            'Device.Optical.Interface.1.',
          ],
        }),
      }
    )
    if (!res.ok) {
      const err = await res.json().catch(() => ({}))
      throw new Error(err.error || 'Refresh failed')
    }
    // Mark cache stale so next fetch refreshes, but keep data for instant display
    const cached = cache.get(serial)
    if (cached) cached.ts = 0
    return res.json()
  }

  async function fetchHosts (serial, refresh = false) {
    // Return cached hosts instantly if available and not forcing refresh
    const cached = hostsCache.get(serial)
    if (cached && !refresh && (Date.now() - cached.ts) < HOSTS_TTL) {
      return cached.data
    }

    const d = getDevice(serial)
    if (!d) return cached?.data || null // return stale cache if device not resolved yet

    const url = `${HUB_URL}/devices/${encodeURIComponent(d._id)}/hosts${refresh ? '?refresh' : ''}`
    const res = await fetch(url)
    if (!res.ok) return cached?.data || null
    const data = await res.json()
    hostsCache.set(serial, { data, ts: Date.now() })
    return data
  }

  /**
   * Fetch port neighbor context for a serial — shows if other ONUs on the same port are affected.
   * Returns { total, online, offline, neighbors: [{ serial, status, cause }], is_mass_outage }
   */
  async function fetchPortContext (serial) {
    if (!serial) return null
    try {
      const res = await fetch(`${HUB_URL}/ai/port-health-by-serial?serial=${encodeURIComponent(serial)}`)
      if (!res.ok) return null
      return await res.json()
    } catch {
      return null
    }
  }

  return {
    deviceMap: readonly(deviceMap),
    oltMap: readonly(oltMap),
    loading: readonly(loading),
    error: readonly(error),
    fetchStatus,
    fetchOltStatus,
    fetchPortContext,
    getDevice,
    getOltData,
    isOnline,
    combinedStatus,
    signalQuality,
    rebootDevice,
    refreshDeviceParams,
    fetchHosts,
  }
}
