import { logger } from '../../../core/utils/logger.js';
import type { IInboundProxyProtocolPolicy, IRouteConfig, IRouteMatch, IRouteAction, TPortRange, TTransportProtocol } from '../models/route-types.js';

/**
 * Validates route configurations for correctness and safety
 */
export class RouteValidator {
  private static readonly VALID_TLS_MODES = ['terminate', 'passthrough', 'terminate-and-reencrypt'];
  private static readonly VALID_ACTION_TYPES = ['forward', 'socket-handler'];
  private static readonly VALID_PROTOCOLS = ['tcp', 'http', 'https', 'ws', 'wss', 'udp', 'quic', 'http3'];
  private static readonly MAX_PORTS = 100;
  private static readonly MAX_DOMAINS = 1000;
  private static readonly MAX_HEADER_SIZE = 8192;

  /**
   * Validate a single route configuration
   */
  public static validateRoute(route: IRouteConfig): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    // Validate route has a name
    if (!route.name || typeof route.name !== 'string') {
      errors.push('Route must have a valid name');
    }

    // Validate match criteria
    if (!route.match) {
      errors.push('Route must have match criteria');
    } else {
      // Validate ports
      if (route.match.ports) {
        const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
        
        if (ports.length > this.MAX_PORTS) {
          errors.push(`Too many ports specified (max ${this.MAX_PORTS})`);
        }
        
        for (const port of ports) {
          if (typeof port === 'number') {
            if (!this.isValidPort(port)) {
              errors.push(`Invalid port: ${port}. Must be between 1 and 65535`);
            }
          } else if (typeof port === 'object' && 'from' in port && 'to' in port) {
            if (!this.isValidPort(port.from)) {
              errors.push(`Invalid port range start: ${port.from}. Must be between 1 and 65535`);
            }
            if (!this.isValidPort(port.to)) {
              errors.push(`Invalid port range end: ${port.to}. Must be between 1 and 65535`);
            }
            if (port.from > port.to) {
              errors.push(`Invalid port range: ${port.from}-${port.to} (start > end)`);
            }
          } else {
            errors.push(`Invalid port configuration: ${JSON.stringify(port)}`);
          }
        }
      }

      // Validate domains
      if (route.match.domains) {
        const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
        
        if (domains.length > this.MAX_DOMAINS) {
          errors.push(`Too many domains specified (max ${this.MAX_DOMAINS})`);
        }
        
        for (const domain of domains) {
          if (!this.isValidDomain(domain)) {
            errors.push(`Invalid domain pattern: ${domain}`);
          }
        }
      }

      // Validate paths
      if (route.match.path) {
        const paths = Array.isArray(route.match.path) ? route.match.path : [route.match.path];
        
        for (const path of paths) {
          if (!this.isValidPath(path)) {
            errors.push(`Invalid path pattern: ${path}`);
          }
        }
      }

      // Validate client IPs
      if (route.match.clientIp) {
        const ips = Array.isArray(route.match.clientIp) ? route.match.clientIp : [route.match.clientIp];
        
        for (const ip of ips) {
          if (!this.isValidIPPattern(ip)) {
            errors.push(`Invalid IP pattern: ${ip}`);
          }
        }
      }

      // Validate inbound PROXY protocol policy
      if (route.match.inboundProxyProtocol) {
        const policy = route.match.inboundProxyProtocol;
        if (!['reject', 'optional', 'required'].includes(policy.mode)) {
          errors.push(`Invalid inbound PROXY protocol mode: ${policy.mode}`);
        }
        if (policy.trustedProxyIPs !== undefined) {
          if (!Array.isArray(policy.trustedProxyIPs) || policy.trustedProxyIPs.length === 0) {
            errors.push('inboundProxyProtocol.trustedProxyIPs must be a non-empty array when specified');
          } else {
            for (const ip of policy.trustedProxyIPs) {
              if (!this.isValidIPPattern(ip)) {
                errors.push(`Invalid trusted proxy IP pattern: ${ip}`);
              }
            }
          }
        }
      }

      // Validate headers
      if (route.match.headers) {
        for (const [key, value] of Object.entries(route.match.headers)) {
          if (key.length > 256) {
            errors.push(`Header name too long: ${key}`);
          }
          
          const headerValue = String(value);
          if (headerValue.length > this.MAX_HEADER_SIZE) {
            errors.push(`Header value too long for ${key} (max ${this.MAX_HEADER_SIZE} bytes)`);
          }
          
          if (!/^[\x20-\x7E]+$/.test(key)) {
            errors.push(`Invalid header name: ${key} (must be printable ASCII)`);
          }
        }
      }

      // Protocol validation removed - not part of IRouteMatch interface
    }

    // Validate action
    if (!route.action) {
      errors.push('Route must have an action');
    } else {
      // Validate action type
      if (!route.action.type || !this.VALID_ACTION_TYPES.includes(route.action.type)) {
        errors.push(`Invalid action type: ${route.action.type}. Must be one of: ${this.VALID_ACTION_TYPES.join(', ')}`);
      }

      // Validate socket-handler (TCP socketHandler or UDP datagramHandler)
      if (route.action.type === 'socket-handler') {
        if (typeof route.action.socketHandler !== 'function' && typeof route.action.datagramHandler !== 'function') {
          errors.push('socket-handler action requires a socketHandler or datagramHandler function');
        }
      }

      // Validate forward target
      if (route.action.type === 'forward') {
        if (!route.action.targets || route.action.targets.length === 0) {
          errors.push('Forward action must have at least one target');
        } else {
          for (const target of route.action.targets) {
            if (!target.host) {
              errors.push('Target must have a host');
            } else if (typeof target.host !== 'string' && !Array.isArray(target.host) && typeof target.host !== 'function') {
              errors.push('Target host must be a string, array of strings, or function');
            }
            
            if (target.port) {
              if (typeof target.port === 'number' && !this.isValidPort(target.port)) {
                errors.push(`Invalid target port: ${target.port}`);
              } else if (target.port !== 'preserve' && typeof target.port !== 'function' && typeof target.port !== 'number') {
                errors.push(`Invalid target port configuration: ${target.port}`);
              }
            }
          }
        }
      }

      // Validate TLS settings
      if (route.action.tls) {
        if (route.action.tls.mode && !this.VALID_TLS_MODES.includes(route.action.tls.mode)) {
          errors.push(`Invalid TLS mode: ${route.action.tls.mode}. Must be one of: ${this.VALID_TLS_MODES.join(', ')}`);
        }

        if (route.action.tls.certificate) {
          if (route.action.tls.certificate !== 'auto' && typeof route.action.tls.certificate !== 'object') {
            errors.push('TLS certificate must be "auto" or a certificate configuration object');
          }
        }

        if (route.action.tls.versions) {
          for (const version of route.action.tls.versions) {
            if (!['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'].includes(version)) {
              errors.push(`Invalid TLS version: ${version}`);
            }
          }
        }
      }

      // QUIC routes require TLS with termination (QUIC mandates TLS 1.3)
      if (route.action.udp?.quic && route.action.type === 'forward') {
        if (!route.action.tls) {
          errors.push('QUIC routes require TLS configuration (action.tls) — QUIC mandates TLS 1.3');
        } else if (route.action.tls.mode === 'passthrough') {
          errors.push('QUIC routes cannot use TLS mode "passthrough" — use "terminate" or "terminate-and-reencrypt"');
        }
      }

      // Protocol quic/http3 requires transport udp or all
      if (route.match?.protocol && ['quic', 'http3'].includes(route.match.protocol)) {
        if (route.match.transport && route.match.transport !== 'udp' && route.match.transport !== 'all') {
          errors.push(`Protocol "${route.match.protocol}" requires transport "udp" or "all"`);
        }
      }
    }

    // Validate security settings
    if (route.security) {
      // Validate IP allow/block lists
      if (route.security.ipAllowList) {
        const allowList = Array.isArray(route.security.ipAllowList) ? route.security.ipAllowList : [route.security.ipAllowList];

        for (const entry of allowList) {
          if (typeof entry === 'string') {
            if (!this.isValidIPPattern(entry)) {
              errors.push(`Invalid IP pattern in allow list: ${entry}`);
            }
          } else if (entry && typeof entry === 'object') {
            if (!this.isValidIPPattern(entry.ip)) {
              errors.push(`Invalid IP pattern in domain-scoped allow entry: ${entry.ip}`);
            }
            if (!Array.isArray(entry.domains) || entry.domains.length === 0) {
              errors.push(`Domain-scoped allow entry for ${entry.ip} must have non-empty domains array`);
            }
          }
        }
      }

      if (route.security.ipBlockList) {
        const blockList = Array.isArray(route.security.ipBlockList) ? route.security.ipBlockList : [route.security.ipBlockList];
        
        for (const ip of blockList) {
          if (!this.isValidIPPattern(ip)) {
            errors.push(`Invalid IP pattern in block list: ${ip}`);
          }
        }
      }

      // Validate rate limits
      if (route.security.rateLimit) {
        if (route.security.rateLimit.maxRequests && route.security.rateLimit.maxRequests < 0) {
          errors.push('Rate limit maxRequests must be positive');
        }
        
        if (route.security.rateLimit.window && route.security.rateLimit.window < 0) {
          errors.push('Rate limit window must be positive');
        }

        if (route.security.rateLimit.onExceeded) {
          const onExceeded = route.security.rateLimit.onExceeded;
          if (!['429', 'challenge'].includes(onExceeded.type)) {
            errors.push('Rate limit onExceeded.type must be "429" or "challenge"');
          }
          if (onExceeded.type === 'challenge' && !onExceeded.challenge) {
            errors.push('Rate limit onExceeded.challenge is required when type is "challenge"');
          }
          if (
            onExceeded.clearanceEffect !== undefined
            && !['bypass-rate-limit', 'none'].includes(onExceeded.clearanceEffect)
          ) {
            errors.push('Rate limit onExceeded.clearanceEffect must be "bypass-rate-limit" or "none"');
          }
        }
      }

      // Validate connection limits
      if (route.security.maxConnections && route.security.maxConnections < 0) {
        errors.push('Max connections must be positive');
      }
    }

    // Validate priority
    if (route.priority !== undefined && (route.priority < 0 || route.priority > 10000)) {
      errors.push('Priority must be between 0 and 10000');
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  /**
   * Validate multiple route configurations
   */
  public static validateRoutes(routes: IRouteConfig[]): { valid: boolean; errors: Map<string, string[]> } {
    const errorMap = new Map<string, string[]>();
    let valid = true;

    // Check for duplicate route names
    const routeNames = new Set<string>();
    for (const route of routes) {
      if (route.name && routeNames.has(route.name)) {
        const existingErrors = errorMap.get(route.name) || [];
        existingErrors.push('Duplicate route name');
        errorMap.set(route.name, existingErrors);
        valid = false;
      }
      if (route.name) {
        routeNames.add(route.name);
      }
    }

    // Validate each route
    for (const route of routes) {
      const result = this.validateRoute(route);
      if (!result.valid) {
        errorMap.set(route.name || 'unnamed', result.errors);
        valid = false;
      }
    }

    // Check for conflicting routes
    const conflicts = this.findRouteConflicts(routes);
    if (conflicts.length > 0) {
      for (const conflict of conflicts) {
        const existingErrors = errorMap.get(conflict.route) || [];
        existingErrors.push(conflict.message);
        errorMap.set(conflict.route, existingErrors);
      }
      valid = false;
    }

    return { valid, errors: errorMap };
  }

  /**
   * Find potential conflicts between routes
   */
  private static findRouteConflicts(routes: IRouteConfig[]): Array<{ route: string; message: string }> {
    const conflicts: Array<{ route: string; message: string }> = [];
    
    // Group routes by port
    const portMap = new Map<number, IRouteConfig[]>();
    
    for (const route of routes) {
      if (route.match?.ports) {
        const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
        
        // Expand port ranges to individual ports
        const expandedPorts: number[] = [];
        for (const port of ports) {
          if (typeof port === 'number') {
            expandedPorts.push(port);
          } else if (typeof port === 'object' && 'from' in port && 'to' in port) {
            for (let p = port.from; p <= port.to; p++) {
              expandedPorts.push(p);
            }
          }
        }
        
        for (const port of expandedPorts) {
          const routesOnPort = portMap.get(port) || [];
          routesOnPort.push(route);
          portMap.set(port, routesOnPort);
        }
      }
    }

    // Check for conflicting catch-all routes on the same port
    for (const [port, routesOnPort] of portMap) {
      const catchAllRoutes = routesOnPort.filter(r => 
        this.isCatchAllDomainMatch(r.match) && !this.hasSourceScope(r.match)
      );
      
      if (catchAllRoutes.length > 1) {
        for (const route of catchAllRoutes) {
          conflicts.push({
            route: route.name || 'unnamed',
            message: `Multiple catch-all routes on port ${port}`
          });
        }
      }
    }

    // Inbound PROXY protocol is listener-scoped. A route-level declaration is
    // a shorthand for the full port/transport listener policy, so every route
    // sharing that listener must either all omit it or all use an identical one.
    const listenerPolicies = new Map<string, { route: string; fingerprint: string }>();
    for (const route of routes) {
      if (!route.match?.ports) {
        continue;
      }

      for (const port of this.expandPorts(route.match.ports)) {
        for (const transport of this.transportsForMatch(route.match.transport)) {
          const key = `${transport}:${port}`;
          const fingerprint = this.inboundProxyPolicyFingerprint(route.match.inboundProxyProtocol);
          const existing = listenerPolicies.get(key);
          if (!existing) {
            listenerPolicies.set(key, {
              route: route.name || 'unnamed',
              fingerprint,
            });
          } else if (existing.fingerprint !== fingerprint) {
            conflicts.push({
              route: route.name || 'unnamed',
              message: `Conflicting inbound PROXY protocol policy on ${transport} port ${port} (differs from ${existing.route})`,
            });
          }
        }
      }
    }

    return conflicts;
  }

  private static expandPorts(ports: TPortRange): number[] {
    const entries = Array.isArray(ports) ? ports : [ports];
    const expandedPorts: number[] = [];
    for (const port of entries) {
      if (typeof port === 'number') {
        expandedPorts.push(port);
      } else if (typeof port === 'object' && 'from' in port && 'to' in port) {
        for (let current = port.from; current <= port.to; current++) {
          expandedPorts.push(current);
        }
      }
    }
    return expandedPorts;
  }

  private static transportsForMatch(transport?: TTransportProtocol): Array<'tcp' | 'udp'> {
    if (transport === 'udp') {
      return ['udp'];
    }
    if (transport === 'all') {
      return ['tcp', 'udp'];
    }
    return ['tcp'];
  }

  private static inboundProxyPolicyFingerprint(policy?: IInboundProxyProtocolPolicy): string {
    if (!policy) {
      return 'default';
    }
    return JSON.stringify({
      mode: policy.mode,
      trustedProxyIPs: [...(policy.trustedProxyIPs ?? [])].sort(),
    });
  }

  private static isCatchAllDomainMatch(match: IRouteMatch): boolean {
    return !match.domains
      || (Array.isArray(match.domains) && match.domains.includes('*'))
      || match.domains === '*';
  }

  private static hasSourceScope(match: IRouteMatch): boolean {
    return Array.isArray(match.clientIp) ? match.clientIp.length > 0 : Boolean(match.clientIp);
  }

  /**
   * Validate port number
   */
  private static isValidPort(port: number): boolean {
    return Number.isInteger(port) && port >= 1 && port <= 65535;
  }

  /**
   * Validate domain pattern
   */
  private static isValidDomain(domain: string): boolean {
    if (!domain || typeof domain !== 'string') return false;
    if (domain === '*') return true;
    if (domain === 'localhost') return true;
    
    // Allow both *.domain and *domain patterns
    // Also allow regular domains and subdomains
    const domainPatterns = [
      // Standard domain with optional wildcard subdomain (*.example.com)
      /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/,
      // Wildcard prefix without dot (*example.com)
      /^\*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/,
      // IP address
      /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
      // IPv6 address
      /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/
    ];
    
    return domainPatterns.some(pattern => pattern.test(domain));
  }

  /**
   * Validate path pattern
   */
  private static isValidPath(path: string): boolean {
    if (!path || typeof path !== 'string') return false;
    if (!path.startsWith('/')) return false;
    
    // Check for invalid characters
    if (!/^[a-zA-Z0-9/_*:{}.-]+$/.test(path)) return false;
    
    // Validate parameter syntax
    const paramPattern = /\{[a-zA-Z_][a-zA-Z0-9_]*\}/g;
    const params = path.match(paramPattern) || [];
    
    for (const param of params) {
      if (param.length > 32) return false;
    }
    
    return true;
  }

  /**
   * Validate IP pattern
   */
  private static isValidIPPattern(ip: string): boolean {
    if (!ip || typeof ip !== 'string') return false;
    if (ip === '*') return true;
    
    // Check for CIDR notation
    if (ip.includes('/')) {
      const [addr, prefix] = ip.split('/');
      const prefixNum = parseInt(prefix, 10);
      
      if (addr.includes(':')) {
        // IPv6 CIDR
        return this.isValidIPv6(addr) && prefixNum >= 0 && prefixNum <= 128;
      } else {
        // IPv4 CIDR
        return this.isValidIPv4(addr) && prefixNum >= 0 && prefixNum <= 32;
      }
    }
    
    // Check for range
    if (ip.includes('-')) {
      const [start, end] = ip.split('-');
      return (this.isValidIPv4(start) && this.isValidIPv4(end)) ||
             (this.isValidIPv6(start) && this.isValidIPv6(end));
    }
    
    // Check for wildcards in IPv4
    if (ip.includes('*') && !ip.includes(':')) {
      const parts = ip.split('.');
      // Allow 1-4 parts for wildcard patterns (e.g., '10.*', '192.168.*', '192.168.1.*')
      if (parts.length < 1 || parts.length > 4) return false;
      
      for (const part of parts) {
        if (part !== '*' && !/^\d{1,3}$/.test(part)) return false;
        if (part !== '*' && parseInt(part, 10) > 255) return false;
      }
      
      return true;
    }
    
    // Regular IP address
    return this.isValidIPv4(ip) || this.isValidIPv6(ip);
  }

  /**
   * Validate IPv4 address
   */
  private static isValidIPv4(ip: string): boolean {
    const parts = ip.split('.');
    if (parts.length !== 4) return false;
    
    for (const part of parts) {
      const num = parseInt(part, 10);
      if (isNaN(num) || num < 0 || num > 255) return false;
    }
    
    return true;
  }

  /**
   * Validate IPv6 address
   */
  private static isValidIPv6(ip: string): boolean {
    // IPv6 validation including IPv6-mapped IPv4 addresses (::ffff:x.x.x.x)
    const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){0,6}|::1|::|::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i;
    return ipv6Pattern.test(ip);
  }

  /**
   * Log validation errors
   */
  public static logValidationErrors(errors: Map<string, string[]>): void {
    for (const [routeName, routeErrors] of errors) {
      logger.log('error', `Route validation failed for ${routeName}:`, {
        route: routeName,
        errors: routeErrors,
        component: 'route-validator'
      });

      for (const error of routeErrors) {
        logger.log('error', `  - ${error}`, {
          route: routeName,
          component: 'route-validator'
        });
      }
    }
  }
}

// ============================================================================
// Functional API (for backwards compatibility with route-validators.ts)
// ============================================================================

/**
 * Validates a port range or port number
 * @param port Port number, port range, or port function
 * @returns True if valid, false otherwise
 */
export function isValidPort(port: any): boolean {
  if (typeof port === 'number') {
    return port > 0 && port < 65536;
  } else if (Array.isArray(port)) {
    return port.every(p =>
      (typeof p === 'number' && p > 0 && p < 65536) ||
      (typeof p === 'object' && 'from' in p && 'to' in p &&
       p.from > 0 && p.from < 65536 && p.to > 0 && p.to < 65536)
    );
  } else if (typeof port === 'function') {
    return true;
  } else if (typeof port === 'object' && 'from' in port && 'to' in port) {
    return port.from > 0 && port.from < 65536 && port.to > 0 && port.to < 65536;
  }
  return false;
}

/**
 * Validates a domain string - supports wildcards, localhost, and IP addresses
 * @param domain Domain string to validate
 * @returns True if valid, false otherwise
 */
export function isValidDomain(domain: string): boolean {
  if (!domain || typeof domain !== 'string') return false;
  if (domain === '*') return true;
  if (domain === 'localhost') return true;

  const domainPatterns = [
    // Standard domain with optional wildcard subdomain (*.example.com)
    /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/,
    // Wildcard prefix without dot (*example.com)
    /^\*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/,
    // IP address
    /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
    // IPv6 address
    /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/
  ];

  return domainPatterns.some(pattern => pattern.test(domain));
}

/**
 * Validates a route match configuration
 * @param match Route match configuration to validate
 * @returns { valid: boolean, errors: string[] } Validation result
 */
export function validateRouteMatch(match: IRouteMatch): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (match.ports !== undefined) {
    if (!isValidPort(match.ports)) {
      errors.push('Invalid port number or port range in match.ports');
    }
  }

  if (match.domains !== undefined) {
    if (typeof match.domains === 'string') {
      if (!isValidDomain(match.domains)) {
        errors.push(`Invalid domain format: ${match.domains}`);
      }
    } else if (Array.isArray(match.domains)) {
      for (const domain of match.domains) {
        if (!isValidDomain(domain)) {
          errors.push(`Invalid domain format: ${domain}`);
        }
      }
    } else {
      errors.push('Domains must be a string or an array of strings');
    }
  }

  if (match.path !== undefined) {
    if (typeof match.path !== 'string' || !match.path.startsWith('/')) {
      errors.push('Path must be a string starting with /');
    }
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

/**
 * Validates a route action configuration
 * @param action Route action configuration to validate
 * @returns { valid: boolean, errors: string[] } Validation result
 */
export function validateRouteAction(action: IRouteAction): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (!action.type) {
    errors.push('Action type is required');
  } else if (!['forward', 'socket-handler'].includes(action.type)) {
    errors.push(`Invalid action type: ${action.type}`);
  }

  if (action.type === 'forward') {
    if (!action.targets || !Array.isArray(action.targets) || action.targets.length === 0) {
      errors.push('Targets array is required for forward action');
    } else {
      action.targets.forEach((target, index) => {
        if (!target.host) {
          errors.push(`Target[${index}] host is required`);
        } else if (typeof target.host !== 'string' &&
                  !Array.isArray(target.host) &&
                  typeof target.host !== 'function') {
          errors.push(`Target[${index}] host must be a string, array of strings, or function`);
        }

        if (target.port === undefined) {
          errors.push(`Target[${index}] port is required`);
        } else if (typeof target.port !== 'number' &&
                  typeof target.port !== 'function' &&
                  target.port !== 'preserve') {
          errors.push(`Target[${index}] port must be a number, 'preserve', or a function`);
        } else if (typeof target.port === 'number' && !isValidPort(target.port)) {
          errors.push(`Target[${index}] port must be between 1 and 65535`);
        }

        if (target.match) {
          if (target.match.ports && !Array.isArray(target.match.ports)) {
            errors.push(`Target[${index}] match.ports must be an array`);
          }
          if (target.match.method && !Array.isArray(target.match.method)) {
            errors.push(`Target[${index}] match.method must be an array`);
          }
        }
      });
    }

    if (action.tls) {
      if (!['passthrough', 'terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
        errors.push(`Invalid TLS mode: ${action.tls.mode}`);
      }

      if (['terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
        if (action.tls.certificate !== 'auto' &&
            (!action.tls.certificate || !action.tls.certificate.key || !action.tls.certificate.cert)) {
          errors.push('Certificate must be "auto" or an object with key and cert properties');
        }
      }
    }
  }

  // QUIC routes require TLS with termination
  if (action.udp?.quic && action.type === 'forward') {
    if (!action.tls) {
      errors.push('QUIC routes require TLS configuration — QUIC mandates TLS 1.3');
    } else if (action.tls.mode === 'passthrough') {
      errors.push('QUIC routes cannot use TLS mode "passthrough"');
    }
  }

  if (action.type === 'socket-handler') {
    if (!action.socketHandler && !action.datagramHandler) {
      errors.push('Socket handler or datagram handler function is required for socket-handler action');
    } else if (action.socketHandler && typeof action.socketHandler !== 'function') {
      errors.push('Socket handler must be a function');
    } else if (action.datagramHandler && typeof action.datagramHandler !== 'function') {
      errors.push('Datagram handler must be a function');
    }
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

/**
 * Validates a complete route configuration
 * @param route Route configuration to validate
 * @returns { valid: boolean, errors: string[] } Validation result
 */
export function validateRouteConfig(route: IRouteConfig): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (!route.match) {
    errors.push('Route match configuration is required');
  }

  if (!route.action) {
    errors.push('Route action configuration is required');
  }

  if (route.match) {
    const matchValidation = validateRouteMatch(route.match);
    if (!matchValidation.valid) {
      errors.push(...matchValidation.errors.map(err => `Match: ${err}`));
    }
  }

  if (route.action) {
    const actionValidation = validateRouteAction(route.action);
    if (!actionValidation.valid) {
      errors.push(...actionValidation.errors.map(err => `Action: ${err}`));
    }
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

/**
 * Validate an array of route configurations
 * @param routes Array of route configurations to validate
 * @returns { valid: boolean, errors: { index: number, errors: string[] }[] } Validation result
 */
export function validateRoutes(routes: IRouteConfig[]): {
  valid: boolean;
  errors: { index: number; errors: string[] }[]
} {
  const results: { index: number; errors: string[] }[] = [];

  routes.forEach((route, index) => {
    const validation = validateRouteConfig(route);
    if (!validation.valid) {
      results.push({
        index,
        errors: validation.errors
      });
    }
  });

  return {
    valid: results.length === 0,
    errors: results
  };
}

/**
 * Check if a route configuration has the required properties for a specific action type
 * @param route Route configuration to check
 * @param actionType Expected action type
 * @returns True if the route has the necessary properties, false otherwise
 */
export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType: string): boolean {
  if (!route.action || route.action.type !== actionType) {
    return false;
  }

  switch (actionType) {
    case 'forward':
      return !!route.action.targets &&
             Array.isArray(route.action.targets) &&
             route.action.targets.length > 0 &&
             route.action.targets.every(t => t.host && t.port !== undefined);
    case 'socket-handler':
      return (!!route.action.socketHandler && typeof route.action.socketHandler === 'function') ||
             (!!route.action.datagramHandler && typeof route.action.datagramHandler === 'function');
    default:
      return false;
  }
}

/**
 * Throws an error if the route config is invalid, returns the config if valid
 * Useful for immediate validation when creating routes
 * @param route Route configuration to validate
 * @returns The validated route configuration
 * @throws Error if the route configuration is invalid
 */
export function assertValidRoute(route: IRouteConfig): IRouteConfig {
  const validation = validateRouteConfig(route);
  if (!validation.valid) {
    throw new Error(`Invalid route configuration: ${validation.errors.join(', ')}`);
  }
  return route;
}
