import { readFileSync } from 'fs';

export interface RabbitConfigSource {
  get(key: string): string | undefined;
}

export interface ResolvedRabbitMqConfig {
  url: string;
  sanitizedEndpoint: string;
  socketOptions: {
    heartbeatIntervalInSeconds: number;
    reconnectTimeInSeconds: number;
    connectionOptions: Record<string, unknown>;
  };
  tlsEnabled: boolean;
}

const bool = (value: string | undefined, fallback = false) => value === undefined ? fallback : value === 'true';

function material(source: RabbitConfigSource, base64Key: string, pathKey: string): Buffer | undefined {
  const encoded = source.get(base64Key);
  if (encoded) return Buffer.from(encoded, 'base64');
  const path = source.get(pathKey);
  return path ? readFileSync(path) : undefined;
}

export function resolveRabbitMqConfig(
  source: RabbitConfigSource,
  serviceName: string,
  requireProductionSafety: boolean,
): ResolvedRabbitMqConfig {
  const explicitUrl = source.get('RABBITMQ_URL');
  const tlsEnabled = bool(source.get('RABBITMQ_TLS_ENABLED'), explicitUrl?.startsWith('amqps://'));
  const protocol = tlsEnabled ? 'amqps' : 'amqp';
  const host = source.get('RABBITMQ_HOST') || '127.0.0.1';
  const port = source.get('RABBITMQ_PORT') || (tlsEnabled ? '5671' : '5672');
  const username = source.get('RABBITMQ_USERNAME') || 'guest';
  const password = source.get('RABBITMQ_PASSWORD') || 'guest';
  const vhost = source.get('RABBITMQ_VHOST') || '/';
  let url = explicitUrl
    ? (tlsEnabled ? explicitUrl.replace(/^amqp:\/\//, 'amqps://') : explicitUrl)
    : `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}/${vhost === '/' ? '' : encodeURIComponent(vhost.replace(/^\//, ''))}`;
  let parsed: URL;
  try { parsed = new URL(url); }
  catch { throw new Error('RabbitMQ configuration contains an invalid URL'); }
  if (explicitUrl && !parsed.username && source.get('RABBITMQ_USERNAME')) {
    parsed.username = username;
    parsed.password = password;
    url = parsed.toString();
  }
  const configuredUser = decodeURIComponent(parsed.username || username);
  const configuredPassword = decodeURIComponent(parsed.password || password);
  const production = source.get('NODE_ENV') === 'production';
  const tlsRequired = bool(source.get('RABBITMQ_TLS_REQUIRED'), true);
  if (production && requireProductionSafety) {
    if (!configuredUser || !configuredPassword || configuredUser === 'guest' || configuredPassword === 'guest') {
      throw new Error('RabbitMQ production credentials must be non-guest and non-empty');
    }
    if (tlsRequired && parsed.protocol !== 'amqps:' && !tlsEnabled) {
      throw new Error('RabbitMQ TLS/amqps is required for production microservices');
    }
  }

  const connectionOptions: Record<string, unknown> = {
    timeout: Number(source.get('RABBITMQ_CONNECTION_TIMEOUT_MS') || 10_000),
    clientProperties: { connection_name: `${serviceName}:${source.get('HOSTNAME') || process.pid}` },
  };
  if (tlsEnabled || parsed.protocol === 'amqps:') {
    connectionOptions.rejectUnauthorized = bool(source.get('RABBITMQ_TLS_REJECT_UNAUTHORIZED'), true);
    const servername = source.get('RABBITMQ_SERVERNAME');
    if (servername) connectionOptions.servername = servername;
    const ca = material(source, 'RABBITMQ_CA_CERT_BASE64', 'RABBITMQ_CA_CERT_PATH');
    const cert = material(source, 'RABBITMQ_CLIENT_CERT_BASE64', 'RABBITMQ_CLIENT_CERT_PATH');
    const key = material(source, 'RABBITMQ_CLIENT_KEY_BASE64', 'RABBITMQ_CLIENT_KEY_PATH');
    if (ca) connectionOptions.ca = [ca];
    if (cert) connectionOptions.cert = cert;
    if (key) connectionOptions.key = key;
    if ((cert && !key) || (!cert && key)) throw new Error('RabbitMQ client certificate and key must be configured together');
  }

  return {
    url,
    sanitizedEndpoint: `${parsed.protocol}//${parsed.hostname}:${parsed.port || (parsed.protocol === 'amqps:' ? '5671' : '5672')}${parsed.pathname}`,
    tlsEnabled: tlsEnabled || parsed.protocol === 'amqps:',
    socketOptions: {
      heartbeatIntervalInSeconds: Number(source.get('RABBITMQ_HEARTBEAT_SECONDS') || 10),
      reconnectTimeInSeconds: Number(source.get('RABBITMQ_RECONNECT_SECONDS') || 5),
      connectionOptions,
    },
  };
}

export function processEnvSource(): RabbitConfigSource {
  return { get: (key) => process.env[key] };
}
