const { fetchPeeringDB } = require("../../services/peeringdb");
const { CITY_COORDS } = require("../../data/city-coords");
const { IX_CITY_MAP } = require("../../data/ix-city-map");

// Batch-fetch facility lat/lon by ID, 25 per request (PeeringDB /fac?id__in= limit),
// bounded by an overall race timeout. Returns {fac_id: {lat, lon}}.
async function fetchFacCoordsBatch(facIds, timeoutMs) {
  const coordMap = {};
  if (facIds.length === 0) return coordMap;
  try {
    const chunks = [];
    for (let i = 0; i < facIds.length; i += 25) chunks.push(facIds.slice(i, i + 25));
    const coordResults = await Promise.race([
      Promise.all(chunks.map(chunk =>
        fetchPeeringDB("/fac?id__in=" + chunk.join(",") + "&fields=id,latitude,longitude").catch(() => null)
      )),
      new Promise(r => setTimeout(() => r([]), timeoutMs)),
    ]);
    (coordResults || []).forEach(res => {
      (res?.data || []).forEach(f => { if (f.latitude && f.longitude) coordMap[f.id] = { lat: f.latitude, lon: f.longitude }; });
    });
  } catch (e) { /* graceful degradation */ }
  return coordMap;
}

// Resolve facility coordinates for the map, then IX locations via ixfac -> fac
// coordinates (max 20 IXs), then the name/CITY_COORDS/IX_CITY_MAP geocode
// fallbacks for IXs PeeringDB has no facility coordinates for.
async function resolveFacilitiesAndIxLocations(facilitiesRaw, ixConnections) {
  // Batch-fetch facility coordinates for map (max 50 facilities)
  const facIds = facilitiesRaw.map(f => f.fac_id).filter(Boolean).slice(0, 50);
  let facCoordMap = await fetchFacCoordsBatch(facIds, 5000);

  const facilities = facilitiesRaw.map(f => ({
    ...f,
    latitude: facCoordMap[f.fac_id] ? facCoordMap[f.fac_id].lat : null,
    longitude: facCoordMap[f.fac_id] ? facCoordMap[f.fac_id].lon : null,
  }));

  // Get IX locations for map via ixfac -> fac coordinates (max 20 IXs)
  const uniqueIxIds = [...new Set(ixConnections.map(c => c.ix_id))].filter(Boolean).slice(0, 20);
  let ixLocations = [];
  if (uniqueIxIds.length > 0) {
    try {
      const ixFacData = await Promise.race([
        fetchPeeringDB("/ixfac?ix_id__in=" + uniqueIxIds.join(",")),
        new Promise(r => setTimeout(() => r(null), 5000)),
      ]);
      const ixFacs = ixFacData?.data || [];
      // Collect unique fac_ids we don't already have coords for
      const extraFacIds = [...new Set(ixFacs.map(f => f.fac_id).filter(id => id && !facCoordMap[id]))].slice(0, 30);
      if (extraFacIds.length > 0) {
        const extraCoords = await fetchFacCoordsBatch(extraFacIds, 4000);
        facCoordMap = { ...facCoordMap, ...extraCoords };
      }
      // Build IX locations: pick first facility with coords per IX
      const ixNameMap = {};
      ixConnections.forEach(c => { if (c.ix_id && c.ix_name) ixNameMap[c.ix_id] = c.ix_name; });
      const seenIx = {};
      ixFacs.forEach(f => {
        if (seenIx[f.ix_id]) return;
        const coords = facCoordMap[f.fac_id];
        if (coords) {
          seenIx[f.ix_id] = true;
          ixLocations.push({ ix_id: f.ix_id, name: ixNameMap[f.ix_id] || f.name || "", city: f.city || "", country: f.country || "", latitude: coords.lat, longitude: coords.lon });
        }
      });
    } catch (e) { /* graceful degradation */ }
  }

  applyIxGeocodeFallback(ixLocations, ixConnections);

  return { facilities, ixLocations };
}

// === IX Location Geocode Fallback ===
// Some IXPs have no facility coordinates in PeeringDB.
// Use ix_name city extraction + hard-coded IX→city map as fallback.
// Mutates ixLocations in place (matches original control flow).
function applyIxGeocodeFallback(ixLocations, ixConnections) {
  var ixIdsWithCoords = new Set(ixLocations.map(function(l) { return l.ix_id; }));
  ixConnections.forEach(function(conn) {
    if (ixIdsWithCoords.has(conn.ix_id)) return;
    var name = conn.ix_name || "";
    if (name) {
      var words = name.toLowerCase().replace(/[^a-z\s]/g, " ").split(/\s+/).filter(Boolean);
      for (var w = 0; w < words.length; w++) {
        if (CITY_COORDS[words[w]]) {
          ixLocations.push({ ix_id: conn.ix_id, name: name, city: words[w].charAt(0).toUpperCase() + words[w].slice(1), country: "", latitude: CITY_COORDS[words[w]][0], longitude: CITY_COORDS[words[w]][1], source: "name_geocode" });
          ixIdsWithCoords.add(conn.ix_id);
          return;
        }
        if (w < words.length - 1) {
          var tw = words[w] + " " + words[w + 1];
          if (CITY_COORDS[tw]) {
            ixLocations.push({ ix_id: conn.ix_id, name: name, city: tw, country: "", latitude: CITY_COORDS[tw][0], longitude: CITY_COORDS[tw][1], source: "name_geocode" });
            ixIdsWithCoords.add(conn.ix_id);
            return;
          }
        }
      }
    }
  });
  // Hard-coded IX ID → city for well-known IXPs whose names don't contain city
  ixConnections.forEach(function(conn) {
    if (ixIdsWithCoords.has(conn.ix_id)) return;
    var city = IX_CITY_MAP[conn.ix_id];
    if (city && CITY_COORDS[city]) {
      ixLocations.push({ ix_id: conn.ix_id, name: conn.ix_name || ("IX " + conn.ix_id), city: city.charAt(0).toUpperCase() + city.slice(1), country: "", latitude: CITY_COORDS[city][0], longitude: CITY_COORDS[city][1], source: "ix_city_map" });
    }
  });
}

module.exports = { resolveFacilitiesAndIxLocations };
