// lib/cloud-ips.js — détecte si une IP appartient à un fournisseur cloud/datacenter (AWS, GCP…).
// Sert à BLOQUER les ouvertures de liens-cadeaux depuis des IP de datacenter (bots/proxies) au wrapper /g/.
// Charge les plages PUBLIÉES (AWS ip-ranges.json, GCP cloud.json) en mémoire + rafraîchit 1×/jour.
// Fail-open : tant que les plages ne sont pas chargées (ou en cas d'erreur), isCloudIp renvoie {cloud:false} → on NE bloque pas par défaut.
const https = require('https')

let V4 = [] // [{base:int, bits, provider}]
let V6 = [] // [{base:BigInt, bits, provider}]
let _loadedAt = 0

// ── Parsing IP ──
function ip4ToInt (ip) { const p = String(ip).split('.'); if (p.length !== 4) return null; let n = 0; for (const o of p) { const x = Number(o); if (!Number.isInteger(x) || x < 0 || x > 255) return null; n = (n * 256) + x } return n >>> 0 }
function ip6ToBig (ip) {
  ip = String(ip).split('%')[0].toLowerCase()
  if (ip.indexOf(':') < 0) return null
  let head, tail
  if (ip.indexOf('::') >= 0) { const parts = ip.split('::'); head = parts[0] ? parts[0].split(':') : []; tail = parts[1] ? parts[1].split(':') : [] } else { head = ip.split(':'); tail = [] }
  const expand = (arr) => { const out = []; for (const g of arr) { if (g.indexOf('.') >= 0) { const q = g.split('.').map(Number); if (q.length === 4 && q.every(x => x >= 0 && x <= 255)) { out.push((((q[0] << 8) | q[1]) >>> 0).toString(16)); out.push((((q[2] << 8) | q[3]) >>> 0).toString(16)) } } else if (g !== '') out.push(g) } return out }
  head = expand(head); tail = expand(tail)
  const mid = 8 - (head.length + tail.length); if (mid < 0) return null
  const groups = head.concat(Array(mid).fill('0'), tail)
  let big = 0n
  for (const g of groups) { const v = parseInt(g || '0', 16); if (isNaN(v)) return null; big = (big << 16n) | BigInt(v) }
  return big
}
function parseCidr (cidr, provider) {
  const [addr, bitsRaw] = String(cidr).split('/'); const bits = parseInt(bitsRaw, 10)
  if (addr.indexOf(':') >= 0) { const base = ip6ToBig(addr); if (base == null || isNaN(bits)) return; V6.push({ base, bits, provider }) } else { const base = ip4ToInt(addr); if (base == null || isNaN(bits)) return; V4.push({ base, bits, provider }) }
}

// ── Détection ──
function isCloudIp (ip) {
  if (!ip) return { cloud: false }
  ip = String(ip).trim().replace(/^::ffff:/i, '') // IPv4-mapped IPv6 → IPv4
  if (ip.indexOf(':') < 0) {
    const n = ip4ToInt(ip); if (n == null) return { cloud: false }
    for (const r of V4) { const mask = r.bits === 0 ? 0 : (0xFFFFFFFF << (32 - r.bits)) >>> 0; if ((n & mask) >>> 0 === (r.base & mask) >>> 0) return { cloud: true, provider: r.provider } }
    return { cloud: false }
  }
  const big = ip6ToBig(ip); if (big == null) return { cloud: false }
  for (const r of V6) { const sh = 128n - BigInt(r.bits); if ((big >> sh) === (r.base >> sh)) return { cloud: true, provider: r.provider } }
  return { cloud: false }
}

// IP(s) candidates d'une requête derrière Traefik : toute la chaîne X-Forwarded-For + l'adresse socket.
function clientIps (req) {
  const out = []
  const xff = req.headers['x-forwarded-for']
  if (xff) for (const p of String(xff).split(',')) { const ip = p.trim(); if (ip) out.push(ip) }
  const xr = req.headers['x-real-ip']; if (xr) out.push(String(xr).trim())
  const ra = req.socket && req.socket.remoteAddress; if (ra) out.push(String(ra).trim())
  return out.filter((v, i, a) => v && a.indexOf(v) === i)
}

// ── Chargement des plages publiées ──
function fetchJson (url) {
  return new Promise((resolve) => {
    https.get(url, { headers: { 'User-Agent': 'targo-hub/cloud-ips' }, timeout: 20000 }, (r) => {
      if (r.statusCode !== 200) { r.resume(); return resolve(null) }
      let b = ''; r.on('data', c => { b += c }); r.on('end', () => { try { resolve(JSON.parse(b)) } catch (e) { resolve(null) } })
    }).on('error', () => resolve(null)).on('timeout', function () { this.destroy(); resolve(null) })
  })
}
async function load () {
  const v4 = [], v6 = []
  const push = (cidr, prov) => { const [a, bRaw] = String(cidr).split('/'); const bits = parseInt(bRaw, 10); if (isNaN(bits)) return; if (a.indexOf(':') >= 0) { const base = ip6ToBig(a); if (base != null) v6.push({ base, bits, provider: prov }) } else { const base = ip4ToInt(a); if (base != null) v4.push({ base, bits, provider: prov }) } }
  const aws = await fetchJson('https://ip-ranges.amazonaws.com/ip-ranges.json')
  if (aws) { for (const p of (aws.prefixes || [])) push(p.ip_prefix, 'AWS'); for (const p of (aws.ipv6_prefixes || [])) push(p.ipv6_prefix, 'AWS') }
  const gcp = await fetchJson('https://www.gstatic.com/ipranges/cloud.json')
  if (gcp) { for (const p of (gcp.prefixes || [])) { if (p.ipv4Prefix) push(p.ipv4Prefix, 'GCP'); if (p.ipv6Prefix) push(p.ipv6Prefix, 'GCP') } }
  // Plages supplémentaires (datacenters fréquents) — ajout manuel léger.
  for (const c of EXTRA_V4) push(c, 'DC')
  if (v4.length + v6.length > 100) { V4 = v4; V6 = v6; _loadedAt = Date.now() } // ne remplace que si le chargement a vraiment ramené des plages
  return { v4: v4.length, v6: v6.length, loaded: !!(v4.length + v6.length) }
}
// DigitalOcean / OVH / Hetzner / Oracle (échantillon ; étendre au besoin).
const EXTRA_V4 = ['159.203.0.0/16', '167.71.0.0/16', '134.209.0.0/16', '157.245.0.0/16', '144.217.0.0/16', '51.222.0.0/16', '5.9.0.0/16', '88.198.0.0/16', '129.213.0.0/16']

let _initPromise = null
function init () { if (!_initPromise) { _initPromise = load().catch(() => ({ loaded: false })); const t = setInterval(() => { load().catch(() => {}) }, 24 * 3600 * 1000); if (t && t.unref) t.unref() } return _initPromise }
function stats () { return { v4: V4.length, v6: V6.length, loadedAt: _loadedAt } }

module.exports = { init, isCloudIp, clientIps, stats, ip4ToInt, ip6ToBig }
