import { exec, execSync } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
 * Represents a port range for forwarding
 */
export interface IPortRange {
  from: number;
  to: number;
}

/**
 * Settings for IPTablesProxy.
 */
export interface IIpTableProxySettings {
  // Basic settings
  fromPort: number | IPortRange | Array<number | IPortRange>; // Support single port, port range, or multiple ports/ranges
  toPort: number | IPortRange | Array<number | IPortRange>;
  toHost?: string; // Target host for proxying; defaults to 'localhost'
  
  // Advanced settings
  preserveSourceIP?: boolean; // If true, the original source IP is preserved
  deleteOnExit?: boolean;     // If true, clean up marked iptables rules before process exit
  protocol?: 'tcp' | 'udp' | 'all'; // Protocol to forward, defaults to 'tcp'
  enableLogging?: boolean;    // Enable detailed logging
  ipv6Support?: boolean;      // Enable IPv6 support (ip6tables)
  
  // Source filtering
  allowedSourceIPs?: string[]; // If provided, only these IPs are allowed
  bannedSourceIPs?: string[];  // If provided, these IPs are blocked
  
  // Rule management
  forceCleanSlate?: boolean;   // Clear all IPTablesProxy rules before starting
  addJumpRule?: boolean;       // Add a custom chain for cleaner rule management
  checkExistingRules?: boolean; // Check if rules already exist before adding
  
  // Integration with PortProxy/NetworkProxy
  netProxyIntegration?: {
    enabled: boolean;
    redirectLocalhost?: boolean; // Redirect localhost traffic to NetworkProxy
    sslTerminationPort?: number; // Port where NetworkProxy handles SSL termination
  };
}

/**
 * Represents a rule added to iptables
 */
interface IpTablesRule {
  table: string;
  chain: string;
  command: string;
  tag: string;
  added: boolean;
}

/**
 * IPTablesProxy sets up iptables NAT rules to forward TCP traffic.
 * Enhanced with multi-port support, IPv6, and integration with PortProxy/NetworkProxy.
 */
export class IPTablesProxy {
  public settings: IIpTableProxySettings;
  private rules: IpTablesRule[] = [];
  private ruleTag: string;
  private customChain: string | null = null;

  constructor(settings: IIpTableProxySettings) {
    // Validate inputs to prevent command injection
    this.validateSettings(settings);
    
    // Set default settings
    this.settings = {
      ...settings,
      toHost: settings.toHost || 'localhost',
      protocol: settings.protocol || 'tcp',
      enableLogging: settings.enableLogging !== undefined ? settings.enableLogging : false,
      ipv6Support: settings.ipv6Support !== undefined ? settings.ipv6Support : false,
      checkExistingRules: settings.checkExistingRules !== undefined ? settings.checkExistingRules : true,
      netProxyIntegration: settings.netProxyIntegration || { enabled: false }
    };
    
    // Generate a unique identifier for the rules added by this instance
    this.ruleTag = `IPTablesProxy:${Date.now()}:${Math.random().toString(36).substr(2, 5)}`;
    
    if (this.settings.addJumpRule) {
      this.customChain = `IPTablesProxy_${Math.random().toString(36).substr(2, 5)}`;
    }

    // Register cleanup handlers if deleteOnExit is true
    if (this.settings.deleteOnExit) {
      const cleanup = () => {
        try {
          this.stopSync();
        } catch (err) {
          console.error('Error cleaning iptables rules on exit:', err);
        }
      };
      
      process.on('exit', cleanup);
      process.on('SIGINT', () => {
        cleanup();
        process.exit();
      });
      process.on('SIGTERM', () => {
        cleanup();
        process.exit();
      });
    }
  }

  /**
   * Validates settings to prevent command injection and ensure valid values
   */
  private validateSettings(settings: IIpTableProxySettings): void {
    // Validate port numbers
    const validatePorts = (port: number | IPortRange | Array<number | IPortRange>) => {
      if (Array.isArray(port)) {
        port.forEach(p => validatePorts(p));
        return;
      }
      
      if (typeof port === 'number') {
        if (port < 1 || port > 65535) {
          throw new Error(`Invalid port number: ${port}`);
        }
      } else if (typeof port === 'object') {
        if (port.from < 1 || port.from > 65535 || port.to < 1 || port.to > 65535 || port.from > port.to) {
          throw new Error(`Invalid port range: ${port.from}-${port.to}`);
        }
      }
    };
    
    validatePorts(settings.fromPort);
    validatePorts(settings.toPort);
    
    // Define regex patterns at the method level so they're available throughout
    const ipRegex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
    const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/;
    
    // Validate IP addresses
    const validateIPs = (ips?: string[]) => {
      if (!ips) return;
      
      for (const ip of ips) {
        if (!ipRegex.test(ip) && !ipv6Regex.test(ip)) {
          throw new Error(`Invalid IP address format: ${ip}`);
        }
      }
    };
    
    validateIPs(settings.allowedSourceIPs);
    validateIPs(settings.bannedSourceIPs);
    
    // Validate toHost - only allow hostnames or IPs
    if (settings.toHost) {
      const hostRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
      if (!hostRegex.test(settings.toHost) && !ipRegex.test(settings.toHost) && !ipv6Regex.test(settings.toHost)) {
        throw new Error(`Invalid host format: ${settings.toHost}`);
      }
    }
  }
  
  /**
   * Normalizes port specifications into an array of port ranges
   */
  private normalizePortSpec(portSpec: number | IPortRange | Array<number | IPortRange>): IPortRange[] {
    const result: IPortRange[] = [];
    
    if (Array.isArray(portSpec)) {
      // If it's an array, process each element
      for (const spec of portSpec) {
        result.push(...this.normalizePortSpec(spec));
      }
    } else if (typeof portSpec === 'number') {
      // Single port becomes a range with the same start and end
      result.push({ from: portSpec, to: portSpec });
    } else {
      // Already a range
      result.push(portSpec);
    }
    
    return result;
  }

  /**
   * Gets the appropriate iptables command based on settings
   */
  private getIptablesCommand(isIpv6: boolean = false): string {
    return isIpv6 ? 'ip6tables' : 'iptables';
  }

  /**
   * Checks if a rule already exists in iptables
   */
  private async ruleExists(table: string, command: string, isIpv6: boolean = false): Promise<boolean> {
    try {
      const iptablesCmd = this.getIptablesCommand(isIpv6);
      const { stdout } = await execAsync(`${iptablesCmd}-save -t ${table}`);
      // Convert the command to the format found in iptables-save output
      // (This is a simplification - in reality, you'd need more parsing)
      const rulePattern = command.replace(`${iptablesCmd} -t ${table} -A `, '-A ');
      return stdout.split('\n').some(line => line.trim() === rulePattern);
    } catch (err) {
      this.log('error', `Failed to check if rule exists: ${err}`);
      return false;
    }
  }

  /**
   * Sets up a custom chain for better rule management
   */
  private async setupCustomChain(isIpv6: boolean = false): Promise<boolean> {
    if (!this.customChain) return true;
    
    const iptablesCmd = this.getIptablesCommand(isIpv6);
    const table = 'nat';
    
    try {
      // Create the chain
      await execAsync(`${iptablesCmd} -t ${table} -N ${this.customChain}`);
      this.log('info', `Created custom chain: ${this.customChain}`);
      
      // Add jump rule to PREROUTING chain
      const jumpCommand = `${iptablesCmd} -t ${table} -A PREROUTING -j ${this.customChain} -m comment --comment "${this.ruleTag}:JUMP"`;
      await execAsync(jumpCommand);
      this.log('info', `Added jump rule to ${this.customChain}`);
      
      // Store the jump rule
      this.rules.push({
        table,
        chain: 'PREROUTING',
        command: jumpCommand,
        tag: `${this.ruleTag}:JUMP`,
        added: true
      });
      
      return true;
    } catch (err) {
      this.log('error', `Failed to set up custom chain: ${err}`);
      return false;
    }
  }

  /**
   * Add a source IP filter rule
   */
  private async addSourceIPFilter(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.allowedSourceIPs && !this.settings.bannedSourceIPs) {
      return true;
    }
    
    const iptablesCmd = this.getIptablesCommand(isIpv6);
    const table = 'nat';
    const chain = this.customChain || 'PREROUTING';
    
    try {
      // Add banned IPs first (explicit deny)
      if (this.settings.bannedSourceIPs && this.settings.bannedSourceIPs.length > 0) {
        for (const ip of this.settings.bannedSourceIPs) {
          const command = `${iptablesCmd} -t ${table} -A ${chain} -s ${ip} -j DROP -m comment --comment "${this.ruleTag}:BANNED"`;
          
          // Check if rule already exists
          if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) {
            this.log('info', `Rule already exists, skipping: ${command}`);
            continue;
          }
          
          await execAsync(command);
          this.log('info', `Added banned IP rule: ${command}`);
          
          this.rules.push({
            table,
            chain,
            command,
            tag: `${this.ruleTag}:BANNED`,
            added: true
          });
        }
      }
      
      // Add allowed IPs (explicit allow)
      if (this.settings.allowedSourceIPs && this.settings.allowedSourceIPs.length > 0) {
        // First add a default deny for all
        const denyAllCommand = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} -j DROP -m comment --comment "${this.ruleTag}:DENY_ALL"`;
        
        // Add allow rules for specific IPs
        for (const ip of this.settings.allowedSourceIPs) {
          const command = `${iptablesCmd} -t ${table} -A ${chain} -s ${ip} -p ${this.settings.protocol} -j ACCEPT -m comment --comment "${this.ruleTag}:ALLOWED"`;
          
          // Check if rule already exists
          if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) {
            this.log('info', `Rule already exists, skipping: ${command}`);
            continue;
          }
          
          await execAsync(command);
          this.log('info', `Added allowed IP rule: ${command}`);
          
          this.rules.push({
            table,
            chain,
            command,
            tag: `${this.ruleTag}:ALLOWED`,
            added: true
          });
        }
        
        // Now add the default deny after all allows
        if (this.settings.checkExistingRules && await this.ruleExists(table, denyAllCommand, isIpv6)) {
          this.log('info', `Rule already exists, skipping: ${denyAllCommand}`);
        } else {
          await execAsync(denyAllCommand);
          this.log('info', `Added default deny rule: ${denyAllCommand}`);
          
          this.rules.push({
            table,
            chain,
            command: denyAllCommand,
            tag: `${this.ruleTag}:DENY_ALL`,
            added: true
          });
        }
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add source IP filter rules: ${err}`);
      return false;
    }
  }

  /**
   * Adds a port forwarding rule
   */
  private async addPortForwardingRule(
    fromPortRange: IPortRange,
    toPortRange: IPortRange,
    isIpv6: boolean = false
  ): Promise<boolean> {
    const iptablesCmd = this.getIptablesCommand(isIpv6);
    const table = 'nat';
    const chain = this.customChain || 'PREROUTING';
    
    try {
      // Handle single port case
      if (fromPortRange.from === fromPortRange.to && toPortRange.from === toPortRange.to) {
        // Single port forward
        const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPortRange.from} ` +
          `-j DNAT --to-destination ${this.settings.toHost}:${toPortRange.from} ` +
          `-m comment --comment "${this.ruleTag}:DNAT"`;
        
        // Check if rule already exists
        if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) {
          this.log('info', `Rule already exists, skipping: ${command}`);
        } else {
          await execAsync(command);
          this.log('info', `Added port forwarding rule: ${command}`);
          
          this.rules.push({
            table,
            chain,
            command,
            tag: `${this.ruleTag}:DNAT`,
            added: true
          });
        }
      } else if (fromPortRange.to - fromPortRange.from === toPortRange.to - toPortRange.from) {
        // Port range forward with equal ranges
        const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPortRange.from}:${fromPortRange.to} ` +
          `-j DNAT --to-destination ${this.settings.toHost}:${toPortRange.from}-${toPortRange.to} ` +
          `-m comment --comment "${this.ruleTag}:DNAT_RANGE"`;
        
        // Check if rule already exists
        if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) {
          this.log('info', `Rule already exists, skipping: ${command}`);
        } else {
          await execAsync(command);
          this.log('info', `Added port range forwarding rule: ${command}`);
          
          this.rules.push({
            table,
            chain,
            command,
            tag: `${this.ruleTag}:DNAT_RANGE`,
            added: true
          });
        }
      } else {
        // Unequal port ranges need individual rules
        for (let i = 0; i <= fromPortRange.to - fromPortRange.from; i++) {
          const fromPort = fromPortRange.from + i;
          const toPort = toPortRange.from + i % (toPortRange.to - toPortRange.from + 1);
          
          const command = `${iptablesCmd} -t ${table} -A ${chain} -p ${this.settings.protocol} --dport ${fromPort} ` +
            `-j DNAT --to-destination ${this.settings.toHost}:${toPort} ` +
            `-m comment --comment "${this.ruleTag}:DNAT_INDIVIDUAL"`;
          
          // Check if rule already exists
          if (this.settings.checkExistingRules && await this.ruleExists(table, command, isIpv6)) {
            this.log('info', `Rule already exists, skipping: ${command}`);
            continue;
          }
          
          await execAsync(command);
          this.log('info', `Added individual port forwarding rule: ${command}`);
          
          this.rules.push({
            table,
            chain,
            command,
            tag: `${this.ruleTag}:DNAT_INDIVIDUAL`,
            added: true
          });
        }
      }
      
      // If preserveSourceIP is false, add a MASQUERADE rule
      if (!this.settings.preserveSourceIP) {
        // For port range
        const masqCommand = `${iptablesCmd} -t nat -A POSTROUTING -p ${this.settings.protocol} -d ${this.settings.toHost} ` +
          `--dport ${toPortRange.from}:${toPortRange.to} -j MASQUERADE ` +
          `-m comment --comment "${this.ruleTag}:MASQ"`;
        
        // Check if rule already exists
        if (this.settings.checkExistingRules && await this.ruleExists('nat', masqCommand, isIpv6)) {
          this.log('info', `Rule already exists, skipping: ${masqCommand}`);
        } else {
          await execAsync(masqCommand);
          this.log('info', `Added MASQUERADE rule: ${masqCommand}`);
          
          this.rules.push({
            table: 'nat',
            chain: 'POSTROUTING',
            command: masqCommand,
            tag: `${this.ruleTag}:MASQ`,
            added: true
          });
        }
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to add port forwarding rule: ${err}`);
      
      // Try to roll back any rules that were already added
      await this.rollbackRules();
      
      return false;
    }
  }

  /**
   * Special handling for NetworkProxy integration
   */
  private async setupNetworkProxyIntegration(isIpv6: boolean = false): Promise<boolean> {
    if (!this.settings.netProxyIntegration?.enabled) {
      return true;
    }
    
    const netProxyConfig = this.settings.netProxyIntegration;
    const iptablesCmd = this.getIptablesCommand(isIpv6);
    const table = 'nat';
    const chain = this.customChain || 'PREROUTING';
    
    try {
      // If redirectLocalhost is true, set up special rule to redirect localhost traffic to NetworkProxy
      if (netProxyConfig.redirectLocalhost && netProxyConfig.sslTerminationPort) {
        const redirectCommand = `${iptablesCmd} -t ${table} -A OUTPUT -p tcp -d 127.0.0.1 -j REDIRECT ` +
          `--to-port ${netProxyConfig.sslTerminationPort} ` +
          `-m comment --comment "${this.ruleTag}:NETPROXY_REDIRECT"`;
        
        // Check if rule already exists
        if (this.settings.checkExistingRules && await this.ruleExists(table, redirectCommand, isIpv6)) {
          this.log('info', `Rule already exists, skipping: ${redirectCommand}`);
        } else {
          await execAsync(redirectCommand);
          this.log('info', `Added NetworkProxy redirection rule: ${redirectCommand}`);
          
          this.rules.push({
            table,
            chain: 'OUTPUT',
            command: redirectCommand,
            tag: `${this.ruleTag}:NETPROXY_REDIRECT`,
            added: true
          });
        }
      }
      
      return true;
    } catch (err) {
      this.log('error', `Failed to set up NetworkProxy integration: ${err}`);
      return false;
    }
  }

  /**
   * Rolls back rules that were added in case of error
   */
  private async rollbackRules(): Promise<void> {
    // Process rules in reverse order (LIFO)
    for (let i = this.rules.length - 1; i >= 0; i--) {
      const rule = this.rules[i];
      
      if (rule.added) {
        try {
          // Convert -A (add) to -D (delete)
          const deleteCommand = rule.command.replace('-A', '-D');
          await execAsync(deleteCommand);
          this.log('info', `Rolled back rule: ${deleteCommand}`);
          
          rule.added = false;
        } catch (err) {
          this.log('error', `Failed to roll back rule: ${err}`);
        }
      }
    }
  }

  /**
   * Sets up iptables rules for port forwarding with enhanced features
   */
  public async start(): Promise<void> {
    // Optionally clean the slate first
    if (this.settings.forceCleanSlate) {
      await IPTablesProxy.cleanSlate();
    }
    
    // First set up any custom chains
    if (this.settings.addJumpRule) {
      const chainSetupSuccess = await this.setupCustomChain();
      if (!chainSetupSuccess) {
        throw new Error('Failed to set up custom chain');
      }
      
      // For IPv6 if enabled
      if (this.settings.ipv6Support) {
        const chainSetupSuccessIpv6 = await this.setupCustomChain(true);
        if (!chainSetupSuccessIpv6) {
          this.log('warn', 'Failed to set up IPv6 custom chain, continuing with IPv4 only');
        }
      }
    }
    
    // Add source IP filters
    await this.addSourceIPFilter();
    if (this.settings.ipv6Support) {
      await this.addSourceIPFilter(true);
    }
    
    // Set up NetworkProxy integration if enabled
    if (this.settings.netProxyIntegration?.enabled) {
      const netProxySetupSuccess = await this.setupNetworkProxyIntegration();
      if (!netProxySetupSuccess) {
        this.log('warn', 'Failed to set up NetworkProxy integration');
      }
      
      if (this.settings.ipv6Support) {
        await this.setupNetworkProxyIntegration(true);
      }
    }
    
    // Normalize port specifications
    const fromPortRanges = this.normalizePortSpec(this.settings.fromPort);
    const toPortRanges = this.normalizePortSpec(this.settings.toPort);
    
    // Handle the case where fromPort and toPort counts don't match
    if (fromPortRanges.length !== toPortRanges.length) {
      if (toPortRanges.length === 1) {
        // If there's only one toPort, use it for all fromPorts
        for (const fromRange of fromPortRanges) {
          await this.addPortForwardingRule(fromRange, toPortRanges[0]);
          
          if (this.settings.ipv6Support) {
            await this.addPortForwardingRule(fromRange, toPortRanges[0], true);
          }
        }
      } else {
        throw new Error('Mismatched port counts: fromPort and toPort arrays must have equal length or toPort must be a single value');
      }
    } else {
      // Add port forwarding rules for each port specification
      for (let i = 0; i < fromPortRanges.length; i++) {
        await this.addPortForwardingRule(fromPortRanges[i], toPortRanges[i]);
        
        if (this.settings.ipv6Support) {
          await this.addPortForwardingRule(fromPortRanges[i], toPortRanges[i], true);
        }
      }
    }
    
    // Final check - ensure we have at least one rule added
    if (this.rules.filter(r => r.added).length === 0) {
      throw new Error('No rules were added');
    }
  }

  /**
   * Removes all added iptables rules
   */
  public async stop(): Promise<void> {
    // Process rules in reverse order (LIFO)
    for (let i = this.rules.length - 1; i >= 0; i--) {
      const rule = this.rules[i];
      
      if (rule.added) {
        try {
          // Convert -A (add) to -D (delete)
          const deleteCommand = rule.command.replace('-A', '-D');
          await execAsync(deleteCommand);
          this.log('info', `Removed rule: ${deleteCommand}`);
          
          rule.added = false;
        } catch (err) {
          this.log('error', `Failed to remove rule: ${err}`);
        }
      }
    }
    
    // If we created a custom chain, we need to clean it up
    if (this.customChain) {
      try {
        // First flush the chain
        await execAsync(`iptables -t nat -F ${this.customChain}`);
        this.log('info', `Flushed custom chain: ${this.customChain}`);
        
        // Then delete it
        await execAsync(`iptables -t nat -X ${this.customChain}`);
        this.log('info', `Deleted custom chain: ${this.customChain}`);
        
        // Same for IPv6 if enabled
        if (this.settings.ipv6Support) {
          try {
            await execAsync(`ip6tables -t nat -F ${this.customChain}`);
            await execAsync(`ip6tables -t nat -X ${this.customChain}`);
            this.log('info', `Deleted IPv6 custom chain: ${this.customChain}`);
          } catch (err) {
            this.log('error', `Failed to delete IPv6 custom chain: ${err}`);
          }
        }
      } catch (err) {
        this.log('error', `Failed to delete custom chain: ${err}`);
      }
    }
    
    // Clear rules array
    this.rules = [];
  }

  /**
   * Synchronous version of stop, for use in exit handlers
   */
  public stopSync(): void {
    // Process rules in reverse order (LIFO)
    for (let i = this.rules.length - 1; i >= 0; i--) {
      const rule = this.rules[i];
      
      if (rule.added) {
        try {
          // Convert -A (add) to -D (delete)
          const deleteCommand = rule.command.replace('-A', '-D');
          execSync(deleteCommand);
          this.log('info', `Removed rule: ${deleteCommand}`);
          
          rule.added = false;
        } catch (err) {
          this.log('error', `Failed to remove rule: ${err}`);
        }
      }
    }
    
    // If we created a custom chain, we need to clean it up
    if (this.customChain) {
      try {
        // First flush the chain
        execSync(`iptables -t nat -F ${this.customChain}`);
        
        // Then delete it
        execSync(`iptables -t nat -X ${this.customChain}`);
        this.log('info', `Deleted custom chain: ${this.customChain}`);
        
        // Same for IPv6 if enabled
        if (this.settings.ipv6Support) {
          try {
            execSync(`ip6tables -t nat -F ${this.customChain}`);
            execSync(`ip6tables -t nat -X ${this.customChain}`);
          } catch (err) {
            // IPv6 failures are non-critical
          }
        }
      } catch (err) {
        this.log('error', `Failed to delete custom chain: ${err}`);
      }
    }
    
    // Clear rules array
    this.rules = [];
  }

  /**
   * Asynchronously cleans up any iptables rules in the nat table that were added by this module.
   * It looks for rules with comments containing "IPTablesProxy:".
   */
  public static async cleanSlate(): Promise<void> {
    await IPTablesProxy.cleanSlateInternal();
    
    // Also clean IPv6 rules
    await IPTablesProxy.cleanSlateInternal(true);
  }
  
  /**
   * Internal implementation of cleanSlate with IPv6 support
   */
  private static async cleanSlateInternal(isIpv6: boolean = false): Promise<void> {
    const iptablesCmd = isIpv6 ? 'ip6tables' : 'iptables';
    
    try {
      const { stdout } = await execAsync(`${iptablesCmd}-save -t nat`);
      const lines = stdout.split('\n');
      const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
      
      // First, find and remove any custom chains
      const customChains = new Set<string>();
      const jumpRules: string[] = [];
      
      for (const line of proxyLines) {
        if (line.includes('IPTablesProxy:JUMP')) {
          // Extract chain name from jump rule
          const match = line.match(/\s+-j\s+(\S+)\s+/);
          if (match && match[1].startsWith('IPTablesProxy_')) {
            customChains.add(match[1]);
            jumpRules.push(line);
          }
        }
      }
      
      // Remove jump rules first
      for (const line of jumpRules) {
        const trimmedLine = line.trim();
        if (trimmedLine.startsWith('-A')) {
          // Replace the "-A" with "-D" to form a deletion command
          const deleteRule = trimmedLine.replace('-A', '-D');
          const cmd = `${iptablesCmd} -t nat ${deleteRule}`;
          try {
            await execAsync(cmd);
            console.log(`Cleaned up iptables jump rule: ${cmd}`);
          } catch (err) {
            console.error(`Failed to remove iptables jump rule: ${cmd}`, err);
          }
        }
      }
      
      // Then remove all other rules
      for (const line of proxyLines) {
        if (!line.includes('IPTablesProxy:JUMP')) { // Skip jump rules we already handled
          const trimmedLine = line.trim();
          if (trimmedLine.startsWith('-A')) {
            // Replace the "-A" with "-D" to form a deletion command
            const deleteRule = trimmedLine.replace('-A', '-D');
            const cmd = `${iptablesCmd} -t nat ${deleteRule}`;
            try {
              await execAsync(cmd);
              console.log(`Cleaned up iptables rule: ${cmd}`);
            } catch (err) {
              console.error(`Failed to remove iptables rule: ${cmd}`, err);
            }
          }
        }
      }
      
      // Finally clean up custom chains
      for (const chain of customChains) {
        try {
          // Flush the chain
          await execAsync(`${iptablesCmd} -t nat -F ${chain}`);
          console.log(`Flushed custom chain: ${chain}`);
          
          // Delete the chain
          await execAsync(`${iptablesCmd} -t nat -X ${chain}`);
          console.log(`Deleted custom chain: ${chain}`);
        } catch (err) {
          console.error(`Failed to delete custom chain ${chain}:`, err);
        }
      }
    } catch (err) {
      console.error(`Failed to run ${iptablesCmd}-save: ${err}`);
    }
  }

  /**
   * Synchronously cleans up any iptables rules in the nat table that were added by this module.
   * It looks for rules with comments containing "IPTablesProxy:".
   * This method is intended for use in process exit handlers.
   */
  public static cleanSlateSync(): void {
    IPTablesProxy.cleanSlateSyncInternal();
    
    // Also clean IPv6 rules
    IPTablesProxy.cleanSlateSyncInternal(true);
  }
  
  /**
   * Internal implementation of cleanSlateSync with IPv6 support
   */
  private static cleanSlateSyncInternal(isIpv6: boolean = false): void {
    const iptablesCmd = isIpv6 ? 'ip6tables' : 'iptables';
    
    try {
      const stdout = execSync(`${iptablesCmd}-save -t nat`).toString();
      const lines = stdout.split('\n');
      const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
      
      // First, find and remove any custom chains
      const customChains = new Set<string>();
      const jumpRules: string[] = [];
      
      for (const line of proxyLines) {
        if (line.includes('IPTablesProxy:JUMP')) {
          // Extract chain name from jump rule
          const match = line.match(/\s+-j\s+(\S+)\s+/);
          if (match && match[1].startsWith('IPTablesProxy_')) {
            customChains.add(match[1]);
            jumpRules.push(line);
          }
        }
      }
      
      // Remove jump rules first
      for (const line of jumpRules) {
        const trimmedLine = line.trim();
        if (trimmedLine.startsWith('-A')) {
          // Replace the "-A" with "-D" to form a deletion command
          const deleteRule = trimmedLine.replace('-A', '-D');
          const cmd = `${iptablesCmd} -t nat ${deleteRule}`;
          try {
            execSync(cmd);
            console.log(`Cleaned up iptables jump rule: ${cmd}`);
          } catch (err) {
            console.error(`Failed to remove iptables jump rule: ${cmd}`, err);
          }
        }
      }
      
      // Then remove all other rules
      for (const line of proxyLines) {
        if (!line.includes('IPTablesProxy:JUMP')) { // Skip jump rules we already handled
          const trimmedLine = line.trim();
          if (trimmedLine.startsWith('-A')) {
            const deleteRule = trimmedLine.replace('-A', '-D');
            const cmd = `${iptablesCmd} -t nat ${deleteRule}`;
            try {
              execSync(cmd);
              console.log(`Cleaned up iptables rule: ${cmd}`);
            } catch (err) {
              console.error(`Failed to remove iptables rule: ${cmd}`, err);
            }
          }
        }
      }
      
      // Finally clean up custom chains
      for (const chain of customChains) {
        try {
          // Flush the chain
          execSync(`${iptablesCmd} -t nat -F ${chain}`);
          
          // Delete the chain
          execSync(`${iptablesCmd} -t nat -X ${chain}`);
          console.log(`Deleted custom chain: ${chain}`);
        } catch (err) {
          console.error(`Failed to delete custom chain ${chain}:`, err);
        }
      }
    } catch (err) {
      console.error(`Failed to run ${iptablesCmd}-save: ${err}`);
    }
  }
  
  /**
   * Logging utility that respects the enableLogging setting
   */
  private log(level: 'info' | 'warn' | 'error', message: string): void {
    if (!this.settings.enableLogging && level === 'info') {
      return;
    }
    
    const timestamp = new Date().toISOString();
    
    switch (level) {
      case 'info':
        console.log(`[${timestamp}] [INFO] ${message}`);
        break;
      case 'warn':
        console.warn(`[${timestamp}] [WARN] ${message}`);
        break;
      case 'error':
        console.error(`[${timestamp}] [ERROR] ${message}`);
        break;
    }
  }
}