// admin-guard.mjs
//
// Pure, testable network-guard helpers for the memind_adm consoles.
//
// Each back-office console (platform super-admin, plaza operations) can be
// pinned to an allowlist of request hosts and/or client IPs, independently.
// This lets the super-admin surface be locked down harder than moderation
// while both still live in one process — and the same knobs keep working
// unchanged once the consoles are split into separate processes.

export function parseList(value) {
  return String(value ?? '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);
}

export function hostAllowed(reqHost, allowedHosts) {
  if (!allowedHosts.length) return true;
  const hostname = String(reqHost ?? '').split(':')[0].toLowerCase();
  return allowedHosts.some((h) => h.toLowerCase() === hostname);
}

function ipv4ToInt(ip) {
  const parts = ip.split('.');
  if (parts.length !== 4) return null;
  let acc = 0;
  for (const part of parts) {
    if (!/^\d{1,3}$/.test(part)) return null;
    const n = Number(part);
    if (n < 0 || n > 255) return null;
    acc = (acc * 256 + n) >>> 0;
  }
  return acc >>> 0;
}

// Strip an IPv4-mapped IPv6 prefix so "::ffff:127.0.0.1" matches "127.0.0.1".
function normalizeIp(ip) {
  return String(ip ?? '').replace(/^::ffff:/i, '');
}

export function ipMatches(clientIp, rule) {
  const ip = normalizeIp(clientIp);
  if (rule.includes('/')) {
    const [base, bitsStr] = rule.split('/');
    const bits = Number(bitsStr);
    const ipInt = ipv4ToInt(ip);
    const baseInt = ipv4ToInt(base);
    if (ipInt === null || baseInt === null) return false;
    if (!Number.isInteger(bits) || bits < 0 || bits > 32) return false;
    if (bits === 0) return true;
    const mask = bits === 32 ? 0xffffffff : (~((1 << (32 - bits)) - 1)) >>> 0;
    return (ipInt & mask) === (baseInt & mask);
  }
  return ip === normalizeIp(rule);
}

export function ipAllowed(clientIp, ipAllowlist) {
  if (!ipAllowlist.length) return true;
  return ipAllowlist.some((rule) => ipMatches(clientIp, rule));
}

/**
 * Build an Express middleware enforcing host + IP allowlists for one console.
 * Returns null when neither list is configured (no-op — common in dev).
 * @param {object} opts
 * @param {string[]} [opts.allowedHosts]
 * @param {string[]} [opts.ipAllowlist]
 * @param {(res, req, reason: 'host'|'ip') => unknown} opts.onDeny
 */
export function buildNetworkGuard({ allowedHosts = [], ipAllowlist = [], onDeny }) {
  if (!allowedHosts.length && !ipAllowlist.length) return null;
  return (req, res, next) => {
    if (!hostAllowed(req.get('host'), allowedHosts)) return onDeny(res, req, 'host');
    if (!ipAllowed(req.ip, ipAllowlist)) return onDeny(res, req, 'ip');
    return next();
  };
}
