const { URL } = require('url');
const dns = require('dns').promises;
const net = require('net');

// ---- IPv4 private / reserved ranges ----
const V4_BLOCKED = [
  /^10\./,
  /^172\.(1[6-9]|2\d|3[01])\./,
  /^192\.168\./,
  /^127\./,
  /^0\./,
  /^169\.254\./,                              // link-local (incl. cloud metadata 169.254.169.254)
  /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, // CGNAT 100.64.0.0/10
];

function isPrivateV4(ip) {
  return V4_BLOCKED.some((re) => re.test(ip));
}

// Convert an IPv4-mapped IPv6 address to dotted IPv4, or null.
// Handles both "::ffff:127.0.0.1" and hex form "::ffff:7f00:1".
function mappedToV4(ip) {
  const m = ip.match(/^::ffff:([0-9a-f.:]+)$/i);
  if (!m) return null;
  const tail = m[1];
  if (tail.includes('.')) return tail;
  const parts = tail.split(':');
  if (parts.length === 2) {
    const hi = parseInt(parts[0], 16);
    const lo = parseInt(parts[1], 16);
    if (Number.isNaN(hi) || Number.isNaN(lo)) return null;
    return [(hi >> 8) & 255, hi & 255, (lo >> 8) & 255, lo & 255].join('.');
  }
  return null;
}

// Returns true if the address is private/reserved (or unrecognised → fail-closed).
function isPrivateIP(ipRaw) {
  if (!ipRaw) return true;
  const ip = String(ipRaw).replace(/^\[|\]$/g, '').toLowerCase();

  const ver = net.isIP(ip);
  if (ver === 4) return isPrivateV4(ip);

  if (ver === 6) {
    const asV4 = mappedToV4(ip);
    if (asV4) return isPrivateV4(asV4);
    if (ip === '::1' || ip === '::') return true;   // loopback / unspecified
    if (/^(fc|fd)/.test(ip)) return true;           // ULA fc00::/7
    if (/^fe[89ab]/.test(ip)) return true;          // link-local fe80::/10
    return false;
  }

  // Not a bare IP literal — caller should not pass hostnames here; treat as unsafe.
  return true;
}

/**
 * Validate URL is safe for server-side requests (no SSRF).
 * Resolves BOTH A and AAAA and fails CLOSED on resolution errors.
 * Returns { safe: true, url, addresses } or { safe: false, error }.
 */
async function validateUrl(rawUrl) {
  if (!rawUrl || typeof rawUrl !== 'string') {
    return { safe: false, error: 'URL обязателен' };
  }

  let parsed;
  try {
    parsed = new URL(rawUrl);
  } catch {
    return { safe: false, error: 'Некорректный URL' };
  }

  if (!['http:', 'https:'].includes(parsed.protocol)) {
    return { safe: false, error: 'Поддерживаются только http и https' };
  }

  // hostname without IPv6 brackets
  const host = parsed.hostname.replace(/^\[|\]$/g, '');

  // Direct IP literal (v4 or v6)
  if (net.isIP(host)) {
    if (isPrivateIP(host)) {
      return { safe: false, error: 'Доступ к внутренним адресам запрещён' };
    }
    return { safe: true, url: rawUrl };
  }

  // Common internal hostnames
  const lower = host.toLowerCase();
  if (lower === 'localhost' || lower.endsWith('.local') || lower.endsWith('.internal') || lower.endsWith('.localhost')) {
    return { safe: false, error: 'Доступ к внутренним адресам запрещён' };
  }

  // Resolve BOTH A and AAAA; fail CLOSED if nothing resolves.
  let addrs = [];
  const [v4, v6] = await Promise.allSettled([dns.resolve4(host), dns.resolve6(host)]);
  if (v4.status === 'fulfilled') addrs = addrs.concat(v4.value);
  if (v6.status === 'fulfilled') addrs = addrs.concat(v6.value);

  if (addrs.length === 0) {
    return { safe: false, error: 'Не удалось разрешить имя хоста' };
  }
  for (const addr of addrs) {
    if (isPrivateIP(addr)) {
      return { safe: false, error: 'Доступ к внутренним адресам запрещён' };
    }
  }
  return { safe: true, url: rawUrl, addresses: addrs };
}

module.exports = { validateUrl, isPrivateIP };
