/**
 * Validates URLs before server-side HTTP fetches (tool proxy, webhooks, etc.)
 * to reduce SSRF risk. Hostname-based only; resolved IPs are not re-checked.
 * 
 * Operator guide: docs/GATEWAY-COVENANT-C1.md (http_fetch outbound policy)
 */

import { env } from '@4runr/shared';

const PRIVATE_IP_RANGES = [
  /^127\./,
  /^10\./,
  /^172\.(1[6-9]|2[0-9]|3[01])\./,
  /^192\.168\./,
  /^169\.254\./,
  /^\[?::1\]?$/,           // IPv6 loopback (with or without brackets)
  /^\[?fe80:/i,            // IPv6 link-local
  /^\[?fc00:/i,            // IPv6 ULA
  /^\[?fd00:/i,            // IPv6 ULA
];

const BLOCKED_HOSTS = new Set([
  '169.254.169.254',
  'metadata.google.internal',
  'metadata',
]);

function isProductionNode(): boolean {
  return (process.env['NODE_ENV'] ?? env.NODE_ENV) === 'production';
}

function isAllowInternalFetch(): boolean {
  return process.env['TOOL_PROXY_HTTP_FETCH_ALLOW_INTERNAL'] === 'true';
}

function isPrivateOrLoopbackHost(host: string): boolean {
  const h = host.toLowerCase();
  if (h === 'localhost' || h === '0.0.0.0') return true;
  for (const range of PRIVATE_IP_RANGES) {
    if (range.test(host)) return true;
  }
  return false;
}

/**
 * @throws Error with a safe message if the URL must not be fetched
 */
export function assertSafeToolProxyHttpUrl(rawUrl: string): URL {
  let url: URL;
  try {
    url = new URL(rawUrl);
  } catch {
    throw new Error('Invalid URL');
  }

  // Protocol must be http or https (check before hostname so file:// etc rejected here)
  if (isProductionNode()) {
    if (url.protocol !== 'https:') {
      throw new Error('Only HTTPS URLs are allowed in production');
    }
  } else if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error('Only HTTP and HTTPS URLs are allowed');
  }

  if (url.username || url.password) {
    throw new Error('URL must not contain embedded credentials');
  }

  const host = url.hostname.toLowerCase();
  if (!host) {
    throw new Error('URL must include a hostname');
  }

  if (BLOCKED_HOSTS.has(host)) {
    throw new Error('Host is not allowed');
  }

  if (!isAllowInternalFetch() && isPrivateOrLoopbackHost(host)) {
    throw new Error('Private or loopback hosts are not allowed for this fetch');
  }

  return url;
}
