/**
 * Network interface detection — extracts the LAN + Tailscale IP discovery logic
 * out of src/app.js so it can be unit-tested in isolation (DC-031 regression guard).
 *
 * Why this lives in its own module instead of inside createApp():
 *   The route handler at /api/v1/network/ips previously had this logic inlined.
 *   A bad refactor dropped the `require('os')` line and left a `collectNetworkInterfaces(os)`
 *   reference that crashed with ReferenceError on every request — and no test caught it
 *   because no test exercised the route handler. By extracting the detector here,
 *   (a) the module can be unit-tested without booting the entire Express app and
 *       its middleware/auth/CSRF stack, and
 *   (b) the route handler in src/app.js becomes a thin adapter that calls
 *       `detectInterfaceIps()` — if a future refactor reintroduces an inline `os`
 *       reference, the inline-block of test will still pass, but the regression
 *       suite around this module will catch any divergence in the detector contract.
 *
 * Public API:
 *   detectInterfaceIps() -> { lan: string|null, tailscale: string|null, all: Array<{name, ip}> }
 *     Scans the host's network interfaces and returns the first matching LAN and
 *     Tailscale IPv4 address (if any), plus the full list of non-internal IPv4
 *     interfaces. Used by /api/v1/network/ips to pre-fill the "Add Service" form
 *     auto-detect UX.
 */

'use strict';

// Host LAN IPv4 ranges per RFC 1918:
//   10.0.0.0/8
//  172.16.0.0/12     -> 172.16.* through 172.31.*
//  192.168.0.0/16
const LAN_RANGE = /^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;

/**
 * Scan the host's network interfaces and return the first LAN and Tailscale IPv4
 * addresses (if found), plus the full list of non-internal IPv4 interfaces.
 *
 * Tailscale IP range: 100.64.0.0/10 (Tailscale assigns addresses from this CGNAT
 * range — the entire 100.64.0.0–100.127.255.255 block).
 *
 * @returns {{lan: string|null, tailscale: string|null, all: Array<{name: string, ip: string}>}}
 */
function detectInterfaceIps() {
  // Use a lazy require so test code that mocks `os` can swap it before this
  // function executes. Production callers hit the real `os` module the first
  // time the route handler runs.
  const os = require('os');
  const all = [];
  let lan = null;
  let tailscale = null;

  const interfaces = os.networkInterfaces();
  for (const [name, addrs] of Object.entries(interfaces)) {
    for (const addr of addrs || []) {
      if (addr.internal || addr.family !== 'IPv4') continue;
      const { address: ip } = addr;
      all.push({ name, ip });
      if (!tailscale && isTailscaleIP(ip)) {
        tailscale = ip;
      } else if (!lan && LAN_RANGE.test(ip)) {
        lan = ip;
      }
    }
  }
  return { lan, tailscale, all };
}

/**
 * Tailscale assigns IPv4 addresses from 100.64.0.0/10 (the CGNAT space carved out
 * for it). The first octet must be 100, the second octet must be in [64, 127].
 *
 * @param {string} ip an IPv4 dotted-quad address (e.g. "100.100.50.25")
 * @returns {boolean} true if `ip` falls in the Tailscale CGNAT range
 */
function isTailscaleIP(ip) {
  if (!ip) return false;
  const parts = ip.split('.');
  if (parts.length !== 4) return false;
  const first = parseInt(parts[0], 10);
  const second = parseInt(parts[1], 10);
  if (Number.isNaN(first) || Number.isNaN(second)) return false;
  return first === 100 && second >= 64 && second <= 127;
}

/**
 * Determine whether an IPv4 address is in one of the RFC 1918 private LAN ranges.
 * @param {string} ip an IPv4 dotted-quad address
 * @returns {boolean}
 */
function isPrivateLanIP(ip) {
  if (!ip) return false;
  return LAN_RANGE.test(ip);
}

module.exports = {
  detectInterfaceIps,
  isTailscaleIP,
  isPrivateLanIP,
  LAN_RANGE,
};
