import type { IAcmeOptions, ISmartProxyOptions } from '../models/interfaces.js';
import type { IRouteAction, IRouteConfig, IRouteMatch, IRouteTarget, ITargetMatch } from '../models/route-types.js';
import type {
  IRustAcmeOptions,
  IRustDefaultConfig,
  IRustProxyOptions,
  IRustChallengeOptions,
  IRustRouteAction,
  IRustRouteConfig,
  IRustRouteMatch,
  IRustRouteTarget,
  IRustTargetMatch,
  IRustRouteUdp,
  TRustHeaderMatchers,
} from '../models/rust-types.js';

const SUPPORTED_REGEX_FLAGS = new Set(['i', 'm', 's', 'u', 'g']);

export function serializeHeaderMatchValue(value: string | RegExp): string {
  if (typeof value === 'string') {
    return value;
  }

  const unsupportedFlags = Array.from(new Set(value.flags)).filter((flag) => !SUPPORTED_REGEX_FLAGS.has(flag));
  if (unsupportedFlags.length > 0) {
    throw new Error(
      `Header RegExp uses unsupported flags for Rust serialization: ${unsupportedFlags.join(', ')}`
    );
  }

  return `/${value.source}/${value.flags}`;
}

export function serializeHeaderMatchers(headers?: Record<string, string | RegExp>): TRustHeaderMatchers | undefined {
  if (!headers) {
    return undefined;
  }

  return Object.fromEntries(
    Object.entries(headers).map(([key, value]) => [key, serializeHeaderMatchValue(value)])
  );
}

export function serializeTargetMatchForRust(match?: ITargetMatch): IRustTargetMatch | undefined {
  if (!match) {
    return undefined;
  }

  return {
    ...match,
    headers: serializeHeaderMatchers(match.headers),
  };
}

export function serializeRouteMatchForRust(match: IRouteMatch): IRustRouteMatch {
  return {
    ...match,
    headers: serializeHeaderMatchers(match.headers),
  };
}

export function serializeRouteTargetForRust(target: IRouteTarget): IRustRouteTarget {
  if (typeof target.host !== 'string' && !Array.isArray(target.host)) {
    throw new Error('Route target host must be serialized before sending to Rust');
  }

  if (typeof target.port !== 'number' && target.port !== 'preserve') {
    throw new Error('Route target port must be serialized before sending to Rust');
  }

  return {
    ...target,
    host: target.host,
    port: target.port,
    match: serializeTargetMatchForRust(target.match),
  };
}

function serializeUdpForRust(udp?: IRouteAction['udp']): IRustRouteUdp | undefined {
  if (!udp) {
    return undefined;
  }

  const { maxSessionsPerIP, ...rest } = udp;

  return {
    ...rest,
    maxSessionsPerIp: maxSessionsPerIP,
  };
}

export function serializeRouteActionForRust(action: IRouteAction): IRustRouteAction {
  const {
    socketHandler: _socketHandler,
    datagramHandler: _datagramHandler,
    forwardingEngine: _forwardingEngine,
    nftables: _nftables,
    targets,
    udp,
    ...rest
  } = action;

  return {
    ...rest,
    targets: targets?.map((target) => serializeRouteTargetForRust(target)),
    udp: serializeUdpForRust(udp),
  };
}

export function serializeRouteForRust(route: IRouteConfig): IRustRouteConfig {
  return {
    ...route,
    match: serializeRouteMatchForRust(route.match),
    action: serializeRouteActionForRust(route.action),
  };
}

function serializeAcmeForRust(acme?: IAcmeOptions): IRustAcmeOptions | undefined {
  if (!acme) {
    return undefined;
  }

  return {
    enabled: acme.enabled,
    email: acme.email,
    environment: acme.environment,
    accountEmail: acme.accountEmail,
    port: acme.port,
    useProduction: acme.useProduction,
    renewThresholdDays: acme.renewThresholdDays,
    autoRenew: acme.autoRenew,
    skipConfiguredCerts: acme.skipConfiguredCerts,
    renewCheckIntervalHours: acme.renewCheckIntervalHours,
  };
}

function serializeDefaultsForRust(defaults?: ISmartProxyOptions['defaults']): IRustDefaultConfig | undefined {
  if (!defaults) {
    return undefined;
  }

  const { preserveSourceIP, ...rest } = defaults;

  return {
    ...rest,
    preserveSourceIp: preserveSourceIP,
  };
}

export function buildChallengeOptionsForRust(settings: ISmartProxyOptions): IRustChallengeOptions | undefined {
  const hasChallengeRoutes = settings.routes.some((route) => Boolean(route.security?.challenge));
  if (!hasChallengeRoutes) {
    return undefined;
  }

  const runtimeOptions = settings.challenge || {};
  return {
    cookieSigningKey: runtimeOptions.cookieSigningKey || '',
    pendingCookieName: runtimeOptions.pendingCookieName || '__smartproxy_challenge_pending',
    clearanceCookieName: runtimeOptions.clearanceCookieName || '__smartproxy_clearance',
    reservedPathPrefix: runtimeOptions.reservedPathPrefix || '/.well-known/smartproxy-challenge',
    relaySocketPath: runtimeOptions.relaySocketPath || '',
    relayTimeoutMs: runtimeOptions.relayTimeoutMs ?? 5_000,
    pendingTtlSeconds: runtimeOptions.pendingTtlSeconds ?? 300,
  };
}

export function buildRustProxyOptions(
  settings: ISmartProxyOptions,
  routes: IRustRouteConfig[],
  acmeOverride?: IAcmeOptions,
  challengeOverride?: IRustChallengeOptions,
): IRustProxyOptions {
  const acme = acmeOverride !== undefined ? acmeOverride : settings.acme;

  return {
    routes,
    preserveSourceIp: settings.preserveSourceIP,
    trustedProxyIPs: settings.trustedProxyIPs,
    sendProxyProtocol: settings.sendProxyProtocol,
    defaults: serializeDefaultsForRust(settings.defaults),
    connectionTimeout: settings.connectionTimeout,
    initialDataTimeout: settings.initialDataTimeout,
    socketTimeout: settings.socketTimeout,
    inactivityCheckInterval: settings.inactivityCheckInterval,
    maxConnectionLifetime: settings.maxConnectionLifetime,
    inactivityTimeout: settings.inactivityTimeout,
    gracefulShutdownTimeout: settings.gracefulShutdownTimeout,
    noDelay: settings.noDelay,
    keepAlive: settings.keepAlive,
    keepAliveInitialDelay: settings.keepAliveInitialDelay,
    maxPendingDataSize: settings.maxPendingDataSize,
    disableInactivityCheck: settings.disableInactivityCheck,
    enableKeepAliveProbes: settings.enableKeepAliveProbes,
    enableDetailedLogging: settings.enableDetailedLogging,
    enableTlsDebugLogging: settings.enableTlsDebugLogging,
    enableRandomizedTimeouts: settings.enableRandomizedTimeouts,
    maxConnectionsPerIp: settings.maxConnectionsPerIP,
    connectionRateLimitPerMinute: settings.connectionRateLimitPerMinute,
    keepAliveTreatment: settings.keepAliveTreatment,
    keepAliveInactivityMultiplier: settings.keepAliveInactivityMultiplier,
    extendedKeepAliveLifetime: settings.extendedKeepAliveLifetime,
    metrics: settings.metrics,
    securityPolicy: settings.securityPolicy,
    challenge: challengeOverride || buildChallengeOptionsForRust(settings),
    acme: serializeAcmeForRust(acme),
  };
}
